Micro Frontends in NextJS with Webpack 5
Webpack 5's Module Federation makes creating micro frontends straightforward. It is also available in NextJS 10+ via an experimental feature flag and it makes integrating micro frontends a breeze. You first build an external micro frontend and deploy it as a "federated module" to your destination of choice. Then you update some configs in your NextJS application and Webpack does the rest. It's kind of magic 🦄.
Kevin Wang
Micro frontends
Oct 21, 2023: I’ve updated my site to use next-mdx-remote
in React
server components, and the previous live micro-frontends that were once where
the following code snippet is, are now broken. I also don’t think anyone
should use micro frontends, and I’m not bothering with fixing these.
I won't go in depth in to what they are (the topic is pretty broad and there are more qualified resources out there), but rather how to add a micro frontend into a NextJS application. To set the mood, here’s an attempt at a brief history recap around web applications and when micro frontends popped up.
Timeline
2000’s
For the most part, web applications have typically been built as monolithic apps leveraging a framework like Rails or Django. Early Twitter, Instagram, and Github are some examples.
2011
Then micro services came into the picture to try and solve organizational problems at scale like enabling greater team autonomy, and some technical issues like improved fault isolation and scalability.
"Microservices" themselves premiered at an event for software architects in 2011, where the term was used to describe a style of architecture that many attendees were experimenting with at the time. Netflix and Amazon were among the early pioneers of microservices.
2013
React was introduced which paved the way for performant client side web applications, and inevitably, monolithic frontends.
2016
ThoughtWorks began assessing a new technique known as "micro frontends" to gain similar benefits that microservices offered, but for the front end.
Read: https://martinfowler.com/articles/micro-frontends.html
2020
Webpack 5 was released and its Module Federation feature has opened the doors to new possibilities in the relatively young micro frontend paradigm.
Micro frontends in a NextJS app
So micro frontends have actually been around for a while now and have been implemented in various ways. One of the newer and exciting additions to the ecosystem is Webpack 5 and its module federation feature.
Note: I may use the terms "micro frontend" and "federated module" interchangeably.
Requirements
To implement a micro frontend in a NextJS app, you’ll need be on NextJS 10 or higher, and you’ll need a federated module.
The micro frontend (aka federated module)
My particular micro frontend — a simple button — is a minimally modified fork of the official module-federation bi-directional example.
It’s built then drag 'n' dropped into a public AWS S3 bucket as a quick 'n' easy proof of concept. The bulk of the magic
happens in the webpack.config.js
with the ModuleFederationPlugin.
The
name
,filename
, andexposes
fields are important as they will be referenced from the consuming application.
The application
NextJS 10 10 comes with Webpack 5 as an experimental feature that you can enable with a feature flag.
NextJS 11 comes with Webpack 5 by default, with zero config
Install NextJS 10+
yarn add next@latest
Set the future.webpack5
flag to true
in next.config.js
, and push a new ModuleFederationPlugin to the plugins list.
The remotes
field is what allows the consuming application to import a federated module. The
name
and filename
values from the federated module are used here:
{someKey}: {name}@domain/path/to/{file}
Implementation
To import a federated module, you can use the dynamic
helper. Any modules that were included in the
exposes
field in the federated module can be used in the import path:
{someKey}/{exposes.Button|exposes.SomeModule|etc.}
Here is a diagram of the actual micro frontend implementation on this site. For now, the architecture is very basic, but could easily be extended to include things like a dedicated CloudFront distribution for the micro frontend bucket, a Lambda@Edge to server side render the micro frontend, or an Api Gateway for individual micro frontend data requirements.
Takeaways
This is a proof of concept for how easy it is to integrate a federated module / micro frontend into a NextJS application. Despite being an overly simple example, it was a fun exploratory project to go through.
With that being said, there was an initial mental hump that was a bit difficult to get over, likely because I didn't realize how broad the topic/pattern of micro frontends was. Webpack 5 and module federation only make up a tiny chunk of the topic.
The Webpack 5 ecosystem is still in infant stages, and the biggest source of knowledge seems to be blog posts by adventurous early adopters. Stack Overflow posts around the topic are also very limited. 😐
Things I still want to explore firsthand are
- shared react state management across multiple unrelated micro frontends
- creating a micro frontend in a different framework, like Vue, Angular, or React Native Web
- importing said micro frontend
- server side rendering federated modules
- data fetching from individual federated modules
- successfully render a tree of multiple federated React function components that use state hooks
- successfully render a tree of multiple federated React stateful class components
Caveats
Static type checking is lost with the dynamic importing of federated modules. However, I can see this being solved by publishing static types to npm.
As of this post and the settings shown, I can't seem to get a sane working example of a federated React component that uses a hook, and it seems to be an inherent incompatibility/limitation between ModuleFederationPlugin and some NextJS inner workings.
I've tried multiple options, including some custom loading components, and have successfully rendered a single-depth component
that uses a useState
hook, but the solution was too convoluted to maintain IMO. The ecosystem is still very immature so
I'm hoping for more official patterns around this to come out soon.
Debugging
In the browser console, you can run this to check for the existence of a federated module.
window.app2.get("./Button").then(factory => factory()).then(mod => console.log(mod))