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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679838236635/6b83f945-36e0-4a98-9e69-883c341faa18.png align="center")

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

%[https://medium.com/swlh/webpack-5-module-federation-a-game-changer-to-javascript-architecture-bcdd30e02669] 

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.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1681590347960/d8b552ba-4028-41f7-a083-d6665a76a6ee.png align="center")

## 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](https://cdn.hashnode.com/res/hashnode/image/upload/v1679859997525/efc2c5ff-f033-4eae-99ee-aa845ee1545c.png align="center")

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.

%[https://twitter.com/scriptedalchemy/status/1567253027312246784] 

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

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1679861757760/858b53be-da5b-4387-b902-8d2e2f231e2c.png align="center")

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.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1681597181073/888f4a0d-8bc2-4f75-891a-855558b07b65.png align="center")

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**](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.
    
    ```javascript
    // 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.
    
    ```javascript
    // 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`
    
    ```javascript
    // 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.

```javascript
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.

```javascript
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](https://github.com/alibek-gao/mf-next-chakra-poc_layout) you can find a demo project for Module Federation with Next.js and Chakra UI.

## More info

* [https://github.com/module-federation](https://github.com/module-federation)
    
* [ZackJackson - Module Federation, With Next.js and SSR](https://youtu.be/yU7ARATZoUU)
    
* [Live from CITYJS CONFERENCE Microfrontends Summit](https://www.youtube.com/live/8033GWqjiB0?feature=share)
