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

Micro frontends

👆 This is a live micro frontend!

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.

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.

webpack.config.js
Note: The name, file, and exposes fields are important as they will be referenced from the consuming application.

The application

NextJS 10 comes with Webpack 5 as an experimental feature that you can enable with a feature flag.

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.

Warning: This is not documented!
next.config.js

The remotes field is what allows the consuming application to import a federated module. The name and file 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}/{expose.Button|exposes.SomeModule|etc.}
.src/pages/[year]/[month]/[day]/[slug.tsx]

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.

architecture diagram for importing a micro frontend
architecture diagram for importing a micro frontend

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 components that use hooks

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.

browser-error

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.

See: https://stackoverflow.com/questions/63942391/webpack-5-module-federation-hooks-in-remote-module-not-working

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))
console-output