Building React App with Module Federation and NextJS/React
Tags: react, javascript, webpack
Published: 2022-05-23 06:40:08

![Module Federation Architecture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/51iia5c7vygwcaz5yowb.png)

This document will take you step-by-step through the tasks required to set up a module federation module, with react app as host with NextJS and React Apps as remote apps. This document's how-to will show you the failing issues I encountered and how I solve them; I hope it will help others when they try to do the same.

**_* Disclaimer for NextJS apps you need the latest version of `@module-federation/nextjs-mf` that is a paid module, you can read more [here](https://app.privjs.com/buy/packageDetail?pkg=@module-federation/nextjs-mf)_**

## 📦 Prerequisites
- Knowledge in Module Federation Concepts and miro-frontends
- NodeJS installed (preferable > 14)
- 2 Running React App with access to `webpack.config.js`
    - Preferable not created using CRA(create react app)
    - At least one React Component
    - One will be the host app
    - The other will a remote app

- Running NextJS App
    - At least one React Component
    - This will be the remote app
- Basic Knowledge in Webpack
- License for _`@module-federation/nextjs-mf`_

## Terminology 
**⬇️ Host:**  It is a top-level app that depends on modules exposed from a remote app
**⬆️ Remote:** Exposes components to another app called a host.

## ⬆️ Configuring Remote App - NextJS
- Use `withFederatedSidecar` in your `next.config.js` of the app that you wish to expose modules from. We'll call this "remote_nextjs_module".

```javascript
    const { withFederatedSidecar } = require("@module federation/nextjs-mf");
    module.exports = withFederatedSidecar({
        name: "remote_nextjs_module",
        filename: "static/chunks/remoteEntry.js",
        exposes: {
            "./BB8": "./components/BB8.js",
        },
        shared: {
        },
    })({
        // your original next.config.js export
        reactStrictMode: true,
    });
```

## ⬆️ Configuring Remote App - React
- Use `ModuleFederationPlugin` in your `webpack.config.js` of the app that you wish to expose modules from. We'll call this "remote_react_module".
- I'm demonstrating here only the implementation of `ModuleFederationPlugin` and not adding all the configuration of `webpack.config.js` of the app


```javascript
    const ModuleFederationPlugin = require('webpack').container.ModuleFederationPlugin;
    plugins: [
        new ModuleFederationPlugin({
            name: 'remote_react_module',
            filename: 'RemoteEntry.js',
            exposes: {
                './Kylo': './src/components/Kylo',
            },
            shared: {
            },
        }),
```
 
## ⬇️ Configuring Host App Host - React
- Use `ModuleFederationPlugin` in your `webpack.config.js` of the app that you wish to consume modules. We'll call this "host_react_module".
- I'm demonstrating here only the implementation of `ModuleFederationPlugin` and not adding all the configuration of `webpack.config.js` of the app

```javascript
    const ModuleFederationPlugin = require('webpack').container.ModuleFederationPlugin;
    // your original webpack.config.js configuration
    plugins: [
        new ModuleFederationPlugin({
            name: 'host_react_module',
            filename: 'remoteEntry.js',
            remotes: {
                remote_nextjs_module: 'remote_nextjs_module@http://localhost:8081/_next/static/chunks/remoteEntry.js',
                remote_react_module: 'remote_react_module@http://localhost:8082/remoteEntry.js',
            },
        shared: {
            react: {
            // Notice shared are NOT eager here.
               requiredVersion: false,
               singleton: true,
        },
    },
    }),
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
  ],
```
- 📝 Configure HTML
    - Go to your `HTML` file and add the following
    ```HTML
        <noscript id="__next_css__DO_NOT_USE__"></noscript>
    ```
    - By default NextJS adds a meta tag in its HTML called: `__next_css__DO_NOT_USE__` to their HTML files
    - We need this tag on our non next apps so the injector can find and load `css` below that tag
 
- Go to your component in the React Host App where you want to consume the remote components
- Use `React.lazy` or low level api to import remotes.
```javascript
    import React, { Suspense } from 'react';
    const Kylo = React.lazy(() => import('remote_react_module/Kylo'));
    const BB8 = React.lazy(() => import('remote_nextjs_module/BB8'));
    function App() {
        return (
            <>
                <Suspense fallback={'loading...'}>
                    <BB8 />
                    <Kylo />
    	        </Suspense>
            </>
            );
    }

export default App;
```

## 🎉 Result
![Blog Result](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/drajhl4xea3zh1u2ofwo.png)
 
- I have a `React` Host App that consumes two remote components and one local component, [here](https://host-react-module.vercel.app/)
- One component from a `NextJS` Remote App, [here](https://remote-nextjs-module.vercel.app/)
- One component from a `React` Remote App, [here](https://remote-react-module.vercel.app/)
- One component from the host App


# ⛑️ Troubleshooting

## - Uncaught Error: Shared module is not available for eager consumption
![Uncaught Error: Shared module is not available for eager consumption](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bpj49ak2nbhdx2uskzty.png)
### Solution
For example, your entry looked like this:
- `index.js`
```javascript
    import App from './App';
    import React from 'react';
    import { createRoot } from 'react-dom/client';
    const container = document.getElementById('root');
    const root = createRoot(container);
    root.render(<App />);
```
- Let's create `bootstrap.js` file and move contents of the entry into it, and import that bootstrap into the entry:
- `index.js`
```javascript
    import('./bootstrap');
```
- `bootstrap.js`

```javascript
    import App from './App';
    import React from 'react';
    import { createRoot } from 'react-dom/client';
    const container = document.getElementById('root');
    const root = createRoot(container);
    root.render(<App />);
```
- More in [here](https://webpack.js.org/concepts/module-federation/#uncaught-error-shared-module-is-not-available-for-eager-consumption)
 
## - Uncaught (in promise) TypeError: Cannot read properties of null (reading 'parentNode')
![Uncaught (in promise) TypeError: Cannot read properties of null (reading 'parentNode')](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f8c07wxb3y9mm6t9ncxw.png)
### Solution
- By default `NextJS` adds a meta tag in its `HTML` called: `__next_css__DO_NOT_USE__` to their `HTML` files
- We need that tag on our non next apps so the injector can find and load `css` below that tag

- `index.html` -  non next app
```HTML
    <!DOCTYPE html>
    <html lang="en">
        <head> </head>
        <noscript id="__next_css__DO_NOT_USE__"></noscript>
        <body>
            <div id="root"></div>
        </body>
    </html>
```

## - Getting 404 for remotes Components
![Getting 404 for remotes Components](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ir2i9q35kuwry4lubebr.png)
### Solution
- `webpack` thinks public path is `/` which is wrong. You want it to calculate the path based on `document.currentScript.src`
- Set `publicPath:auto` in your `webpack.config.js`
- Not adding all the configuration of `webpack.config.js` of the app

```javascript
    output: {
        publicPath: 'auto',
    },
```



## 🔗 Resources
- Github repo [link](https://github.com/OmerHerera/module-federation-post)
- App
    - Host: [Link for React App Hosted at Vercel](https://host-react-module.vercel.app/)
    - Remote: [For NextJS App Hosted at Vercel](https://remote-nextjs-module.vercel.app/)
    - Remote App: [For React App Hosted at Vercel](https://remote-react-module.vercel.app/)
- [Module Federation Examples](https://github.com/module-federation/module-federation-examples/tree/master/react-nextjs)