DEV Community

Cover image for Building React App with Module Federation and NextJS/React
Omher
Omher

Posted on • Updated on

Building React App with Module Federation and NextJS/React

Module Federation Architecture

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

📦 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".
    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,
    });
Enter fullscreen mode Exit fullscreen mode

⬆️ 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
    const ModuleFederationPlugin = require('webpack').container.ModuleFederationPlugin;
    plugins: [
        new ModuleFederationPlugin({
            name: 'remote_react_module',
            filename: 'RemoteEntry.js',
            exposes: {
                './Kylo': './src/components/Kylo',
            },
            shared: {
            },
        }),
Enter fullscreen mode Exit fullscreen mode

⬇️ 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
    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',
    }),
  ],
Enter fullscreen mode Exit fullscreen mode
  • 📝 Configure HTML

    • Go to your HTML file and add the following
        <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.

    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;
Enter fullscreen mode Exit fullscreen mode

🎉 Result

Blog Result

  • I have a React Host App that consumes two remote components and one local component, here
  • One component from a NextJS Remote App, here
  • One component from a React Remote App, here
  • 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

Solution

For example, your entry looked like this:

  • index.js
    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 />);
Enter fullscreen mode Exit fullscreen mode
  • Let's create bootstrap.js file and move contents of the entry into it, and import that bootstrap into the entry:
  • index.js
    import('./bootstrap');
Enter fullscreen mode Exit fullscreen mode
  • bootstrap.js
    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 />);
Enter fullscreen mode Exit fullscreen mode

- Uncaught (in promise) TypeError: Cannot read properties of null (reading 'parentNode')

Uncaught (in promise) TypeError: Cannot read properties of null (reading 'parentNode')

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

    <!DOCTYPE html>
    <html lang="en">
        <head> </head>
        <noscript id="__next_css__DO_NOT_USE__"></noscript>
        <body>
            <div id="root"></div>
        </body>
    </html>
Enter fullscreen mode Exit fullscreen mode

- Getting 404 for remotes Components

Getting 404 for remotes Components

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
    output: {
        publicPath: 'auto',
    },
Enter fullscreen mode Exit fullscreen mode

🔗 Resources

Discussion (4)

Collapse
sohammondal profile image
Soham Mondal

Hi, thank you for the article. The links are not working.

Collapse
omher profile image
Omher Author

Thanks,
Links are working now
Thanks for your input

Collapse
stivncastillo profile image
Stiven Castillo

Thanks, is there any other solution for module-federation/nextjs-mf?

Collapse
omher profile image
Omher Author

Not that I'm aware, also myself was researching/looking, and in the end, all the solutions/forum sent me back to use the plugin.