Micro-frontend with React and Next.js
Tags: react, nextjs
Published: 2022-08-10 20:28:31

**Written by [Harsh Patel](https://blog.logrocket.com/author/harshpatel/)✏️**

Working on a large-scale project and managing its codebase can be a big challenge for teams. Though micro-frontends have been in the picture for a while now, they are getting increasingly popular because of their unique features and usability. 

Micro-frontends are particularly helpful because multiple teams can work on individual modules of the same project without worrying about other modules. With micro-frontends, it doesn’t matter how many modules will be added to a current system. 

In this article, we’ll cover the basics of what a micro-frontend is and how to implement it using Next.js. We’ll also discuss the advantages of using micro-frontends in your applications.

*   [Introduction to micro-frontends](#introduction-micro-frontends)
*   [Implementing a micro-frontend with Next.js](#implementing-micro-frontend-next-js)
    *   [Prerequisites](#prerequisites)
    *   [Setting up the micro-frontends](#setting-micro-frontends)
    *   [Execution and results](#execution-results)
*   [Advantages of micro-frontends](#advantages-micro-frontends)
    *   [Deployment and security](#deployment-security)
    *   [Scalability](#scalability)
    *   [Faster development](#faster-development)
    *   [Easy testing](#easy-testing)

## Introduction to micro-frontends <a name="introduction-micro-frontends">

For starters, micro-frontends don’t follow any particular structure and have no fixed boundaries. 

So how small is a micro-frontend? This is still unanswered. The bottom line is that you should split your project up so that the user experience won’t be disturbed. This process may be painful because it’ll likely include multiple whiteboard revisions. 

Your project will likely evolve as time passes and you may need to revise your micro-frontend as you go along. <figcaption>![Micro Frontend Structure](https://blog.logrocket.com/wp-content/uploads/2022/07/micro-frontend-structure.png) Image source: https://microfrontends.com/.<figcaption> 

React is a popular frontend tech stack known for its usefulness and features. Using micro-frontends with React is a cherry on top! And that’s where Next.js comes in. 

Next.js has a wide number of benefits, including:

*   Inbuilt router support. No extra package needed
*   Inbuilt CSS and TypeScript support
*   Automatically set, page-based routes
*   Easily built for production
*   Image optimization and internationalized SDKs
*   Inbuilt serverless functions (API routes)

So now let’s see how to create a micro-frontend with Next.js!

## Implementing a micro-frontend with Next.js <a name="implementing-micro-frontend-next-js">

[We will use module federation](https://blog.logrocket.com/building-micro-frontends-webpacks-module-federation/), which is technically a [webpack v5 feature](https://webpack.js.org/concepts/module-federation/). It allows multiple builds of a single application and runs as a monolith. 

Some people may think module federation is a new JavaScript feature, but it’s actually just an architecture principle that dynamically loads code from other builders. This is great if you want to add a new micro-frontend to the existing system; you can do so quickly without affecting what’s currently there.

### Prerequisites <a name="prerequisites">

Assuming you already have Node installed on your machine, we’re going to build three Next.js micro-frontends. We will expose a component in the first micro-frontend and expose a common function in the second. We will then use those exposed items in our third micro-frontend, essentially making it the consumer of each exported module. 

Your Next.js app should be v10.2 or later in order to [support webpack 5](https://nextjs.org/docs/messages/webpack5). Otherwise, you need to use an external package in order to support module federation. For this tutorial, I’m using Next.js v12.2.0.

### Setting up the micro-frontends <a name="setting-micro-frontends">

First, create three frontends in a single directory by running the given command:

```shell
 mkdir next_microfrontend
> npx create-next-app fe1
> npx create-next-app fe2
> npx create-next-app fe3
```

In the first frontend, or `fe1`, we will create a `Header` component that will be exposed. We’ll do this in the file `src/component/Header.jsx`:

```javascript
import * as React from 'react'

const Header = () => {
  return (
    <div
      style={{
        background: 'black',
        width: '100%',
        height: '100px',
        color: 'white',
        textAlign: 'center',
        display: 'flex',
        justifyContent: 'left',
        alignItems: 'center',
        fontSize: '24px',
        paddingLeft: '15px'
      }}>
      Name
    </div>
  );
};

export default Header;
```

Now, to make it work, we need to add it to the `index.js` page:

```javascript
import styles from '../styles/Home.module.css'
import Header from '../src/component/Header'

export default function Home() {
  return (
    <div className={styles.container}>
      <main className={styles.main}>
        <Header />
      </main>
    </div>
  )
}
```

If you want to see an output, run `npm run dev` and visit `http://localhost:3000/`. It should look like this: ![Micro Frontend Setup Output](https://blog.logrocket.com/wp-content/uploads/2022/07/micro-frontend-setup-output.png) 

Now, we have to expose our component to make it globally available for another micro-frontend. To do so, we need to change `next.config.js` as follows:

```javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  webpack5: true, // Need to make it true for some versions of Next JS
  distDir: 'build', // Defined build directory
  webpack: (config, options) => { // webpack configurations
    config.plugins.push(
      new options.webpack.container.ModuleFederationPlugin({
        name:"fe1",
        filename: "remoteEntry.js", // remote file name which will used later
        remoteType: "var",
        exposes: { // expose all component here.
          "./header": "./src/component/Header"
        },
        shared: [
          {
            react: {
              eager: true,
              singleton: true,
              requiredVersion: false,
            }
          },
          {
            "react-dom": {
              eager: true,
              singleton: true,
              requiredVersion: false,
            }
          },
        ]
      })
    )
    return config
  }
}

module.exports = nextConfig
```

When we build `fe1`, you can find the JavaScript file used in another micro-frontend at the location `http://localhost:[PORT]/build/remoteEntry.js`. 

Great, we’ve created components in `fe1` and in `fe2`! We’ll now create a common function to expose. 

Let’s create one function in `fe2`:

```javascript
// utils/getSquareRoot.js
const squareRoot = (number) => {
  return Math.sqrt(number)
}

export default squareRoot;
```

Now let’s configure `next.config.js` for the same:

```javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  distDir: 'build',
  webpack: (config, options) => {
    config.plugins.push(
      new options.webpack.container.ModuleFederationPlugin({
        name:"fe2",
        filename: "remoteEntry_2.js",
        remoteType: "var",
        exposes: {
          "./squareRoot": "./utils/getSquareRoot"
        },
        shared: [
          {
            react: {
              eager: true,
              singleton: true,
              requiredVersion: false,
            }
          },
          {
            "react-dom": {
              eager: true,
              singleton: true,
              requiredVersion: false,
            }
          },
        ]
      })
    )
    return config
  }
}

module.exports = nextConfig
```

Once we build it, `http://localhost:[PORT]/build/remoteEntry_2.js` will be ready to use. 

Let’s treat `fe3` as a consumer. We will use the exported component of `fe1` and the function of `fe2`. 

First, let’s configure `next.config.js`:

```javascript
/** @type {import('next').NextConfig} */
const path = require('path');
const nextConfig = {
  reactStrictMode: true,
  distDir: 'build',
  webpack: (config, options) => {
    config.plugins.push(
      new options.webpack.container.ModuleFederationPlugin({
        name:"fe3",
        filename: 'static/consumerFile.js'
        remoteType: "var",
        remotes: {
            fe1: options.isServer ? path.resolve(../fe1/build/remoteEntry.js) : 'fe1',
            fe2: options.isServer ? path.resolve(../fe1/build/remoteEntry_2.js) : 'fe2',
        },
        shared: [
          {
            react: {
              eager: true,
              singleton: true,
              requiredVersion: false,
            }
          },
          {
            "react-dom": {
              eager: true,
              singleton: true,
              requiredVersion: false,
            }
          },
        ]
      })
    )
    return config
  }
}

module.exports = nextConfig
```
Here, you can see that we defined `remote` in the webpack configuration. The job of `remote` is to consume from the given URL and make content available for that app. It will accept remote or local dependencies based on the condition that we specified. 

To use that file, we need to update the `_document.js` file listed under `pages`:

```javascript
import { Html, Head, Main, NextScript } from 'next/document'

export default function Document() {
  return (
    <Html>
      <script src="http://localhost:3000/build/remoteEntry.js" />
      <script src="http://localhost:3001/build/remoteEntry_2.js" />
      <Head />
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  )
}
```

Let’s import that module into the `index.js` file and use it.

```javascript
import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'

// Importing modules
const Header = (await import('fe1/header')).default;
const squareRoot = (await import('app1/getSquareRoot')).default;

export default function Home() {
  return (
    <div className={styles.container}>
      <Head>
        <Header />
      </Head>
      <main className={styles.main}>
        <h1 className={styles.title}>
           Square root of 4: {squareRoot(4)}
        </h1>
      </main>
    </div>
  )
}
```

### Execution and results <a name="execution-results">

Now it’s time to check whether it’s working or not. First, build and start `fe1` and `fe2`. Start `fe3` by running `npm start dev` and go to its respective URL to check the output. ![Execution Results](https://blog.logrocket.com/wp-content/uploads/2022/07/execution-results.png) 

Woah! We just consumed the content of two frontend codes into our main frontend! 

🚀 Let’s do some magic. 

Go to `fe1 → src → component` and change the value from `name` to `name updated`. Then, rerun `f2`. 

You can see the new code that was updated in `fe1` without doing anything. Amazing, right?

## Advantages of micro-frontends <a name="advantages-micro-frontends">

Simply put, micro-frontends make web applications more maintainable. If you’ve ever been part of a large application, you know it’s very tedious to manage everything. 

[Micro-frontends work similarly to the divide and conquer rule](https://blog.logrocket.com/micro-frontend-apps-single-spa/). Now, let’s understand the most important and valuable aspects of working with micro-frontends.

### Deployment and security <a name="deployment-security">

A significant advantage of micro-frontend architecture is that you can separate a single body into individual pieces that can be deployed independently. When deploying alone, you can maintain and build without working on other pieces. 

[Vercel](https://vercel.com/blog/monorepos) supports an individual repo of different frontends, irrespective of language and framework to deploy them together. Apart from that, you can use deployment services like [Netlify](https://www.netlify.com/). Once it’s deployed, you can use it as an individual frontend only. 

When the frontend is used by a large audience, it needs to be secure and robust. To make it safe, you can use an SSL Certificate like Wildcard or a single domain, multi-domain, or [SAN SSL Certificate](https://www.cheapsslshop.com/uc-san-ssl-certificates). One SAN or multi-domain SSL Certificate can secure multiple sites and subdomains.

### Scalability <a name="scalability">

There are multiple frameworks built with JavaScript, but if you want to align developers with different backgrounds on the same project, will it even be possible? The answer is yes with micro-frontends! 

You can combine React, Vue, and Angular on a single project by taking advantage of micro-frontend architecture. It generates the best result in the end in my opinion.

### Faster development <a name="faster-deployment">

Now you know that your team can work independently. Great, no more unnecessary chaos! 

Your team can develop and deploy their frontend whenever they want. Releases can be done quickly because there are zero dependencies associated with individual modules. 

The main goal of defining the micro-frontend is faster iterations.

### Easy testing <a name="easy-testing">

It’s better to test individual pieces of an application before jumping into integration testing. That can be achieved here! 

Teams will test the micro-frontend before testing the application, thus decreasing the chances of bugs going into the real system. 

Apart from that, other advantages are a small code base that’s easily maintainable and the ability to quickly add or remove any module from the system.

## Conclusion

In this article, we discussed the concept of micro-frontends and gave examples. Hopefully, now you know that it’s easy to adopt and has some great features! 

Next.js is pretty clean and follows simple micro-frontend architecture. I’m excited to see what Next brings in the future regarding microarchitecture and server-side rendering with micro-frontend. 

Personally, I like the micro-frontend coding style because it’s easily maintained among teams. Apart from that, frontend building and security are also managed pretty elegantly. It’s a very exciting opportunity for a team because in the future, this concept will likely grow beyond limitations! 

Later, you won’t find any differences between a micro-frontend and single skeletal system.

---

## [LogRocket](https://logrocket.com/signup): Full visibility into production Next.js apps

Debugging Next applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try [LogRocket](https://logrocket.com/signup). 

[![LogRocket signup](https://blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png)](https://logrocket.com/signup)

[LogRocket](https://logrocket.com/signup) is like a DVR for web and mobile apps, recording literally everything that happens on your Next app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.