Let’s Build Micro Frontends with NextJS and Module Federation!
Let’s Build Micro Frontends with NextJS and Module Federation!
That headline is a mouth-full, I know!
In the past several years I have been working on distributed and multiple teams as well as being a pretty early adopter of NextJS (since around V2.0!) in production. I’ve worked on micro frontends with shared npm
packages while trying to orchestrate one cohesive user experience.
It was and is hard.
That’s why I have been closely following the latest developments in the field, and since I’ve heard about Webpack 5 Module Federation, I was curious how and when it would work with an amazing framework such as NextJS.
I guess the title and all those buzzwords need a little breakdown and explaining before we get down to business, so… here we go!
What are Micro Front Ends?
Micro Front Ends are like microservices for the front end. Think about it as an encapsulated, self-contained piece of code or component that can be consumed anywhere. To quote micro-frontends.org:
_“_The idea behind Micro Frontends is to think about a website or web app as a composition of features which are owned by independent teams. Each team has a distinct area of business or mission it cares about and specializes in. A team is cross functional and develops its features end-to-end, from database to user interface.”
Source: https://microfrontends.com/
You can read more about this concept in the provided link above or here. The key core concepts to remember:
- Technology agnostic
- Isolated Team Code
- Build a resilient site / App
There are several frameworks and approaches to implement this architecture, but this is not the subject of this post. I will be focusing on sharing code.
What’s Module Federation?
Technically speaking, Module Federation is a Webpack v5 feature that allows separate (Webpack) builds from a single application. However, it’s much more than that…
To paraphrase Zack Jackson (don’t remember where I heard it or saw it), one of the creators of Module Federation:
“It is a distributed application architecture. Independently deployed bundles working as a monolith at runtime.”
So, in a few bullet points:
- It’s a type of JavaScript architecture.
- It allows a JavaScript application to dynamically load code from another application
- It allows haring dependencies — if an application consuming a federated module does not have a dependency needed by the federated code — Webpack will download the missing dependency from that federated build origin.
- Orchestrated at runtime not build time — no need for servers — universal
Module Federation is a tool-based approach to implementing micro front-end architecture.
It is important not to confuse Module Federation
with Webpack [DllPlugin](<https://webpack.js.org/plugins/dll-plugin/>)
which is a tool mostly focused on improving build-time performance. It can be used to build apps that depend on DLLs (Dynamic Link Library), but this can cause deploy delays, there is the extra infrastructure for compile-time dependency, it needs to rebuild when parts change (which causes deploy delays), and it is highly dependent on external code with no fail-safe. In summary, DLLs don't scale with multiple applications and require a lot of manual work for sharing.
Module Federation, on the other hand, is highly flexible while allowing only less deploy delay due to needing only the shared code and app to be built. It is similar to Apollo GraphQL federation but applied to JavaScript modules — browser and Node.js
.
Some terminology that is useful to know when talking about Module Federation:
- Host: A Webpack build that is initialized first during a page load
- Remote: Another Webpack build, where part of it is being consumed by a “host”
- Bidirectional-hosts: can consume and be consumed
- Omnidirectional-hosts: A host that behaves like a remote & host at the same time
I could blabber on a lot more about this, but if you want to learn more you can visit the official website, you can get the “Practical Module Federation” book, or you can check out the resources section.
What’s NextJS?
If you’re not familiar with the frontend/React ecosystem or you have been living under a rock, NextJS is a React framework for building hybrid static and server-side rendered React application.
Basically, it takes off a lot of the hassle of configuring, tinkering, and retrofitting what it takes to get a React application (or website) to Production.
It has a large variety of features out of the box that just makes any web developer grin like a giddy school girl.
To name a few key features:
- Zero configuration
- TypeScript Support
- File-system routing
- Built-in serverless functions (AKA API routes)
- Code splitting and bundling
You can also hear a little bit about what NextJS is in a talk I gave (my first one! pardon my nervousness there 😅) — keep in mind this was a year and half ago from the time of this writing and the NextJS team have added a whole lot of features and optimizations since. You can also check out how we used NextJS at Culture Trip.
For the sake of this post, it is important to remember that frameworks have limitations and in this tutorial, we are fighting some of the limitations NextJS has. The team behind NextJS has made incredible strides in a short period of time. However, to be able to use Module Federation we will need to work around some key aspects, such as no Webpack v5 support (yet) and the framework is not fully async.
What are we going to build?
We’re going to build 2 Next JS apps:
- Remote App (App 1)- will expose a React component and 2 functions
- Consumer (App 2) — will consume code/components from the first app.
(You can watch this great video by Jack Herrington if that’s more your medium.)
⚡ If you want to skip all of this and see all of the code, here’s a link to the repo.
So.. after that’s out of our way…
Let’s do it!
First Steps:
- Create a folder to hold both apps.
- To kick start the first app go into the created folder and run:
npx create-next-app app1
- Kick start the second (notice that this time its
app2
):
npx create-next-app app2
Ok, now we should have 2 apps with NextJS with a version that should be >9.5.6
(higher than)
If you want to stop and try to run them to see they work, just go to their folders and start them off with:
yarn run dev
Now, to use Module Federation, we need Webpack v5, but alas, at the time of this writing Next’s latest version still runs Webpack 4. 😢
But don’t panic yet! Luckily for us, our friend Zack Jackson has us covered with a little nifty package for this transition period called @module-federation/nextjs-mf
!
Setting up our remote app:
Step 1
Go into app1
and run:
yarn add @module-federation/nextjs-mf
Step 2
To use Webpack 5 with our Next apps, we’re going to need to add resolutions to our package.json
:
"resolutions": {
"webpack": "5.1.3"
},
What this does is tell our package manager to use this specific version of Webpack we want to use. But because we’ve used create-next-app
to bootstrap our app, we now need to clean up our node_modules
:
> yarn install
Our boilerplate code is almost ready. What we are missing at this point are the modules we would want to expose to our consumer app.
Let’s add some.
Step 3
First, we’ll create just a simple Nav
component:
Now just to make sure it’s working we’ll add it to our index.js
page and see it render:
If we run yarn dev
in app1
folder and go to localhost:3000
we should see something like this:
partial snapshot from our NextJS App1
Step 4
We’ll add two functions to export as well:
Step 5
After these steps, we should be able to use configure our Module Federation Webpack plugin. So, we need to create a next.config.js
file in the root folder and add this:
Step 6
Next, we need to add pages/_document.js
:
ℹ️ Side note:
for easing this process it is possible to install @module-federation/nextjs-mf
globally (yarn global add @module-federation/nextjs-mf
) and from app2
folder run:
nextjs-mf upgrade -p 3001
This will setup up your package.json
, _document.js
, and next.config.js
from the exposing app set up steps (2, 5, 6) as well as set up the running script for this app to run on PORT:3001
to avoid port clashes.
However, the caveat of this method (at the time of this writing) is that for some reason this changes our NextJS version and nexjs-mf
package version to older ones (in package.json
):
Just be aware if you use this method.
Setting up our consumer app:
If you’ve opted out of using the above method, make sure you’re package.json
looks like this:
Then we need to repeat the same steps as in Step1 and Step2 from the exposing app (add resolutions, remove node_modules
and reinstall), just make sure you're targeting app2
folder.
Next, create your next.config.js
:
Then add _document.js
:
Now we can start consuming modules from app1! 🎉🎉🎉
Let’s import those modules in our pages/index.js
:
Let’s check that everything works as expected:
> yarn dev
Go to your browser and open [localhost:3001](<http://localhost:3001>)
(app2) and this is what you should see:
We were able to consume a component and 2 modules from app1
inside of app2
! 🚀🚀🚀
This is where some more magic comes in:
- Go to
app1/nav
and change thebackgroundColor
property to something else likehotpink
and hit save. - Stop
app2
server and rerun it withyarn dev
again
If you refresh localhost:3001
you should see this result:
This is what should be rendered after we’re done.
What happened here? We were able to simulate a code change in app1
that was received in app2
without making any changes to the actual code of app2
!
Issues and Caveats Along The Way
When I first started off playing around with this setup I ran into an issue where I got a blank screen on the consumer app, apparently, it was due to the naming of my apps and folders. I’ve even opened up an issue about this in the next-mf
package. In short, Don't use kebab case names and pay attention to the file paths 🤷🏽 🤦🏾.
Another important note is that exposing components and pages as modules work well, but there are issues when you try to use NextJS Link
component.
Lastly, note that you cannot expose _app.js
as a shared module.
Deployment
I thought it would be cool to see this project running in a production environment, so I went on and tried to deploy the two apps to 2 popular cloud hosting services:
Vercel: Attempted to deploy there, didn’t work due to Webpack 5 resolutions and a clash in the platform. I have opened a ticket with their support system but still have yet to resolve the issue.
Netlify: As it is, Netlify only support sites to be deployed with the JAMStack architecture, so it only supports NextJS with static HTML export. When running a build locally, I was able to get both apps working while sharing modules even when using next export
- the important file remoteEntryMerged.js
was created in the .next
build folder:
However, after deploying with the correct environment variables in place, for some reason that file is missing from the sources:
Hopefully, I will be able to sort one of these out at some point. When and if I do, I will update. But as it seems, to get this sort of stack running in an actual production environment there is some tinkering to do. I do believe that if you try to just copy the build folder as it outputted locally to an S3 bucket or something similar, it should probably work.
Conclusion
In this post, we’ve seen how to set up and work with Module Federation and NextJS which allows us to share code and components, which in a way, is what allows micro frontends.
This is probably only a temporary solution to get this set up working until NextJS upgrades to Webpack 5.
One thing to keep in mind with Module Federation and using this type of architecture is that it comes with a slew of challenges as well. How to manage versions of federated modules is still in its early days, only a handful of people have actually used it in production. There is a solution being worked on by Zack Jackson (and I’m helping out! 😎) called Federation Dashboard which uses “Module Federation Dashboard Plugin”, but it’s still in the making…
Another challenge might be shared modules sharing breaking contracts or APIs with consuming apps.
Then again, these are solvable problems, just ones that haven’t been iterated enough through yet.
I am a strong believer in the technologies and architecture I’ve touched on in this post and I’m excited to see what the future holds!
If you’ve enjoyed this post please share and clap 👏. Also, if you have any questions, please ask in the comments.
Thank you! 🙏