Let’s Build Micro Frontends with NextJS and Module Federation!

Featured on Hashnode

Let’s Build Micro Frontends with NextJS and Module Federation!

Image for post

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.”

Image for post

Image for post

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:

  1. Remote App (App 1)- will expose a React component and 2 functions
  2. 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:

Image for post

Image for post

Now just to make sure it’s working we’ll add it to our index.js page and see it render:

Image for post

Image for post

If we run yarn dev in app1 folder and go to localhost:3000 we should see something like this:

Image for post

Image for post

partial snapshot from our NextJS App1

Step 4

We’ll add two functions to export as well:

Image for post

Image for post

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:

Image for post

Image for post

Step 6

Next, we need to add pages/_document.js:

Image for post

Image for post

ℹ️ 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):

Image for post

Image for post

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:

Image for post

Image for post

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:

Image for post

Image for post

Then add _document.js:

Image for post

Image for post

Now we can start consuming modules from app1! 🎉🎉🎉

Image for post

Image for post

Let’s import those modules in our pages/index.js:

Image for post

Image for post

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 the backgroundColor property to something else like hotpink and hit save.
  • Stop app2 server and rerun it with yarn dev again

If you refresh localhost:3001 you should see this result:

Image for post

Image for post

This is what should be rendered after we’re done.Image for post

Image for post

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:

Image for post

Image for post

However, after deploying with the correct environment variables in place, for some reason that file is missing from the sources:

Image for post

Image for post

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! 🙏

Resources