Skip to main content

Command Palette

Search for a command to run...

Micro-frontends with Next.js and Module Federation

Server-side rendered micro-frontends solution

Updated
8 min read
Micro-frontends with Next.js and Module Federation

Imagine a typical e-commerce app with a header containing a menu, search bar, user account, and cart, as well as various pages such as the home page, product description page, and checkout page. These pages and components are typically developed by different teams. With traditional frontend development approaches, coordinating these different teams can be a challenge, especially when multiple teams need to work on the same application concurrently.

Micro frontends offer a solution to this problem by applying the principles of microservices to the frontend. By breaking it down into small, independent parts, each team can develop and deploy their parts independently, without worrying about affecting other components.

There are several ways to build micro frontends, each one has its pros and cons. In this article, we will focus on a particular method for building a micro frontend app, which involves Module Federation and Next.js.

Module federation intro

Module Federation is a feature introduced in Webpack 5 that enables developers to dynamically share and consume JavaScript modules (also known as dependencies or components) across multiple applications at runtime. This approach to code sharing allows for better collaboration between development teams working on separate parts of an application.

Simply speaking a monolithic frontend application can be divided into smaller parts with separate repositories and independent build and deployment. As a result, there will be a Host application that consumes modules from smaller Remote applications.

Next.js intro

Next.js is a React framework that provides web applications with server-side rendering and static website generation. It has a file system-based routing system, where each file in the /pages directory corresponds to a specific route in the application. React components exported from these files are rendered when users navigate to those routes. And with /components folder typically containing reusable components that can be imported and used across different pages.

Next.js also provides a powerful feature called getServerSideProps. This function allows developers to fetch data from an external API or database during server-side rendering. The data can be fetched and processed on the server, resulting in faster initial load times and better SEO.

/pages/index.js - represents home page

While Next.js is powerful and offers a lot of features for building a modern frontend application, it can also be challenging to maintain as the codebase grows in size and complexity. This is because Next.js has a monolithic nature, where a single project serves as a client application for multiple services or features.

To overcome this issue, Module Federation can be used to divide the Next.js app into smaller, more independent parts. Thankfully, the Module Federation Team has announced that all their work regarding Next.js will be available in open source, enabling developers to leverage this approach in their projects.

Next.js with Module Federation

By using Module Federation with Next.js, the application can be divided into a Host app and one or more Remote apps. The Host app is responsible for rendering the application shell and can consume code from the Remote apps. These Remote apps are built and deployed separately, allowing for independent development and testing.

As an example, suppose we have a Home page in our Next.js application consisting of various components such as Hero, Promo, Search, and FeaturedProducts. Instead of importing these components locally, Module Federation enables us to import them from Remote applications.

Each remote component can have its own getServerSideProps, this means that remote components can be responsible for both data fetching and rendering their data. Implementation details are hidden which enables development teams to work autonomously on their respective micro frontends, leading to more efficient collaboration and faster development cycles.

Moreover, changes made to remote components can be automatically reflected in the Host app at runtime, without any additional deployment or reboot. This enables a highly agile development process, where developers can make changes to remote components and see the results immediately in the Host app.

In a typical micro frontend architecture utilizing Module Federation, each page may import multiple modules from remote applications. However, it is quite common for a page to import only one primary module that represents the complete page or a significant portion of it from a remote application.

This approach has some advantages:

  1. Simplified Integration: Importing a single module per page simplifies the integration process, as there are fewer dependencies to manage and coordinate.

  2. Clear Responsibility: Each remote application is responsible for a specific page or functionality, resulting in a clear separation of concerns and making it easier to understand and maintain the overall application.

  3. Faster Load Times: Loading a single module per page can potentially reduce the amount of data fetched and processed by the browser, leading to improved performance and faster load times.

  4. Enhanced Autonomy: Development teams can work independently on their respective pages or micro frontends without affecting other parts of the application, fostering better collaboration and efficient development processes.

  5. Easier Updates: With a single module per page, updates or changes can be made in isolation without impacting other pages, streamlining the deployment process.

Despite these benefits, it's important to consider the specific requirements and constraints of a project to determine the optimal approach. In some cases, importing multiple modules per page might be necessary to achieve the desired functionality or maintainability.

Setting up

It is highly recommended to reference this repository, featuring various Module Federation application examples: https://github.com/module-federation/module-federation-examples

Assuming there are two empty Next.js projects created by create-next-app with installed @module-federation/nextjs-mf, one is Host and another is Remote. Host will run on port 3000, Remote on port 3001.

There are only three steps to make Module Federation work with Next.js:

  1. On the Remote app add NextFederationPlugin to next.config.js and define modules ( components or pages) that need to be shared.

     // remote/next.config.js
     const { NextFederationPlugin } = require('@module-federation/nextjs-mf');
    
     const nextConfig = {
       reactStrictMode: true,
       webpack(config, { isServer }) {
         config.plugins.push(
           new NextFederationPlugin({
             name: 'remote',
             filename: 'static/chunks/remoteEntry.js',
             exposes: {
               // specify exposed pages and components
               './SomePage': './pages/somePage.js',
               './SomeComponent': './components/someComponent.js'  
             },
             shared: {
                // specify shared dependencies 
                // read more in Shared Dependencies section
             },
           })
         );
    
         return config;
       },
     }
    
  2. On the Host app add NextFederationPlugin to next.config.js and specify remotes that should be consumed.

     // host/next.config.js
     const { NextFederationPlugin } = require('@module-federation/nextjs-mf');
    
     const remotes = (isServer) => {
       const location = isServer ? 'ssr' : 'chunks';
       return {
         // specify remotes
         remote: `remote@http://localhost:3001/_next/static/${location}/remoteEntry.js`,
       };
     }
    
     const nextConfig = {
       reactStrictMode: true,
       webpack(config, { isServer }) {
         config.plugins.push(
           new NextFederationPlugin({
             name: 'host',
             filename: 'static/chunks/remoteEntry.js',
             remotes: remotes(isServer),
             exposes: {
               // Host app also can expose modules
             }
           })
         );
    
         return config;
       },
     }
    
  3. Now remote modules can be consumed by the Host app using next/dynamic

     // host/pages/someLocalPage.js
     import dynamic from 'next/dynamic';
    
     const RemotePage = dynamic(() => import('remote/SomePage'));
    
     export function LocalPage(props) {
       return <RemotePage {...props} />
     }
    
     export const getServerSideProps = async (ctx) => {
       const remotePage = await import('remote/SomePage');
    
       if (remotePage.getServerSideProps) {
         return remotePage.getServerSideProps(ctx)
       }
    
       return {
         props: {},
       }
     }
    
     export default LocalPage;
    

Shared dependencies

In the process of developing web applications, often needed to use third-party dependencies. This is true for both traditional apps and micro frontend apps, where Host and Remote apps might share dependencies like CSS-in-JS, state management, or data-fetching libraries.

To avoid duplicating resources and keep things running smoothly, the Module Federation Plugin configuration has a shared option. This lets developers mark certain dependencies as shared resources across different apps or components in a micro frontend setup.

Through the shared option — remotes will depend on host dependencies, if the host does not have a dependency, the remote will download its own. No code duplication, but built-in redundancy.

Here is an example with a shared Chakra UI library.

const { NextFederationPlugin } = require('@module-federation/nextjs-mf');

const nextConfig = {
  webpack(config, { isServer }) {
    config.plugins.push(
      new NextFederationPlugin({
        name: 'shop',
        filename: 'static/chunks/remoteEntry.js',
        remotes: {},
        exposes: {
          './Featured': './pages/featured.js',
          './Search': './pages/search.js',
        },
        shared: {
          '@emotion/': {
            eager: true,
            requiredVersion: false,
            singleton: true,
          },
          '@chakra-ui/': {
            eager: true,
            requiredVersion: false,
            singleton: true,
          },
        },
      })
    );

    return config;
  },
}

module.exports = nextConfig

The singleton option is used to ensure that only a single instance of a shared module is used across all federated applications. When you set the singleton property to true, you're telling Webpack that there must be only one instance of the shared module loaded and shared between all applications.

Hot reload

To enable hot reloading of the node server (not client) in production, you need add revalidate utility into _document.js. This is recommended, without it - servers will not be able to pull remote updates without a full restart.

import { revalidate, FlushedChunks, flushChunks }from '@module-federation/nextjs-mf/utils';
import Document, { Html, Head, Main, NextScript } from 'next/document';

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    if(process.env.NODE_ENV === "development" && !ctx.req.url.includes("_next")) {
      await revalidate().then((shouldReload) =>{
        if (shouldReload) {
          ctx.res.writeHead(302, { Location: ctx.req.url });
          ctx.res.end();
        }
      });
    } else {
      ctx?.res?.on("finish", () => {
        revalidate()
      });
    }

    const chunks = await flushChunks()

    const initialProps = await Document.getInitialProps(ctx);
    return {
      ...initialProps,
      chunks
    };
  }

  render() {
    return (
      <Html>
        <Head>
          <FlushedChunks chunks={this.props.chunks} />
        </Head>
        <body>
        <Main />
        <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

Demo project

Here you can find a demo project for Module Federation with Next.js and Chakra UI.

More info

K

I read it well, but the link between Next.js and Module Federation you're talking about has so much to lose compared to what you get.

  1. You need to import the components of the remoteApp as a dynamic import (ssr false). The reason is that if you do static or dynamic import (ssr true), you will get a hydration error. You will use the entry.js of the remoteApp that was built at the time of the build on the server, but what if the remoteApp screen was modified in the meantime? You will see a 100% hydration error.

  2. Dynamic import (ssr false) fails to render your components on the server, which eventually tarnishes one of Next.js' main objectives of optimizing SEO. It can also cause pages to appear slightly late, page layout to appear slightly broken, blinking, or shaking. Wouldn't it be better to split into monorepo and float servers in multiple locations and redirect different services??

  3. Every time a remote app page is added, you have to keep adding and sync pages with shell, so what's the point of doing this hassle at the expense of the aforementioned?

P

Great article. How can you import the hooks from another remote? It would fail if It is dynamic

J

Hi Alibek,

Have you tried using NextJs SSG (Static site generation). we are able to create a next app with SSG and it was running fine with production build (ex: out folder). however when we try to load static remote page (remoteEntry.js) in host application. getStaticProps are not being called.

What we are trying to achieve is to create a next page with SSG and deploy the production build in any S3 bucket (no runtime/ serveless), refer remoteEntry.js in host application

If you any insights on this please help

J

The NextJS component level is necessary. how to write an SSR method. Is there another most recent bundle available?

J

How can I expose a component which needs to do an API call to full fill that component? And when I'm using that component in another repo it should be rendered on the server side.

And tell me is there any way to do a serverside call in the next js component level not in the page level?

A

What you want it is the new feature of Next.js called App Router, which is not supported by Module federation.

Here you can find how to fetch data on server-side directly in React components https://nextjs.org/docs/app/building-your-application/data-fetching/fetching

J

Alibek Kazhibekov For example, in the page router, we have a banner component, to show a banner image we need to do an API call to fetch the image. And I want a reusable component that I can expose and use in another repository and I want all this to happen from the server side. And How to call the SSR method inside the component.

J

I'm facing one issue with Module Federation For NextJs with Tailwind CSS. How to config tailwind css in the NextJS remote app and host app? Or How to get the tailwind CSS code for the remote app to host app and assets images?

A

I recommend you to read this article if you already haven't.

https://malcolmkee.com/blog/using-tailwindcss-with-module-federation/

It is not nextjs, but similar approach should work in your case.