Building React App with Module Federation and NextJS/React

omher

Omher

Posted on May 23, 2022

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

💖 💪 🙅 🚩
omher
Omher

Posted on May 23, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related