Bhavik Shah
Bhavik Shah

Reputation: 19

Module Federation or Micro Frontend is not working in Next js version 12.2.0

I followed below steps to achieve module federation in next js.

  1. Create a component Insurance_Detail.tsx in repository cm-insurance-web inside src/node/components folder, which will be exposed. Below is the next.config.js file.
const assetPrefix = '/jobs-assets';
const nextConfig = {
  assetPrefix,
  env: {
    assetPrefix
  },
  experimental: {
    images: {
      unoptimized: true
    }
  },
  reactStrictMode: true,
  webpack5: true,
  srcDir: 'src/node/',
  //distDir: 'build',
  webpack: (config, options) => { // webpack configurations
    config.plugins.push(
        new options.webpack.container.ModuleFederationPlugin({
          name:"InsuranceA",
          filename: "static/chunks/pages/cm_insurance_web.js", // remote file name which will used later
          remoteType: "var",
          exposes: { // expose all component here.
            **"./InsuranceDetail": "./components/Insurance_Details.tsx"**
          },
          shared: [
            {
              react: {
                eager: true,
                singleton: true,
                requiredVersion: false,
              }
            },
            {
              "react-dom": {
                eager: true,
                singleton: true,
                requiredVersion: false,
              }
            },
          ]
        })
    )
      config.cache = false;
    config.output.publicPath = 'http://localhost:3000/_next/';
    return config
  }
}

module.exports = nextConfig
  1. When we build repo cm-insurance-web using command npm run build, we can see javascript file is being created inside src/node/.next/static/chunks/pages/cm_insurance_web.js. This project repo is running on 3000 port on localhost.

  2. Now need to consume this javascript in other repository let say cm-job-board-web. Let's create consumer app. Below is the next.config.js file for it

/** @type {import('next').NextConfig} */
const assetPrefix = '/jobs-assets';
const path = require('path');
const nextConfig = {
  assetPrefix,
  env: {
    assetPrefix
  },
  basePath: '/search-jobs',
  experimental: {
    images: {
      unoptimized: true
    }
  },
  reactStrictMode: true,
  srcDir: 'src/node/',
  webpack: (config, options) => {
    config.plugins.push(
        new options.webpack.container.ModuleFederationPlugin({
          name:"jobboardWeb",
          filename: "static/chunks/cm_job_board_web.js",
          remoteType: "var",
          remotes: {
              InsuranceA: JSON.stringify('InsuranceA@http://localhost:3000/jobs-assets/_next/static/chunks/pages/cm_insurance_web.js')
          },exposes: {},
            shared: [
                {
                    react: {
                        eager: true,
                        singleton: true,
                        requiredVersion: false,
                    }
                },
                {
                    "react-dom": {
                        eager: true,
                        singleton: true,
                        requiredVersion: false,
                    }
                },
            ]
        })
    )
      config.cache = false;
    return config
  },
  webpack5: true
}

module.exports = nextConfig
  1. add script tag in _app.tsx file of consumer app as follows:
import { AppProps } from "next/app";
import "bootstrap/dist/css/bootstrap.css";
import "../styles/globals.scss";
import Layout from "../components/layout";
import { persistor, store } from "../store/store";
import { Provider } from "react-redux";
import { PersistGate } from "redux-persist/integration/react";
import Authentication from "../config/auth.gaurd";
import Head from "next/head";
import React from "react";
import Script from "next/script";

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <Layout>
        <>
            <Script src="http://localhost:3000/jobs-assets/_next/static/chunks/pages/cm_insurance_web.js" />
            <Head>
                <link rel="preconnect" href="https://fonts.googleapis.com"/>
                <link rel="preconnect" href="https://fonts.gstatic.com"/>
                <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@100;200;300;400;500;600;700&display=swap"/>
                <link rel="shortcut icon" href="/favicon2.ico"/>
                <title>Jobboard Search</title>
            </Head>
            <Script id="gtm-script" strategy="afterInteractive">
                {`(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
                    new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
                    j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
                    'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
                    })(window,document,'script','dataLayer','GTM-TKJH8RR');`
                }
            </Script>
            <Provider store={store}>
                <PersistGate loading={null} persistor={persistor}>
                    <Authentication>
                        <noscript dangerouslySetInnerHTML={{ __html:
                            `<iframe src="https://www.googletagmanager.com/ns.html?id=GTM-TKJH8RR"
                            height="0" width="0" style="display:none;visibility:hidden"></iframe>
                            `}}>
                        </noscript>
                        <Component {...pageProps} />
                    </Authentication>
                </PersistGate>
            </Provider>
        </>
    </Layout>
  );
}

export default MyApp;
  1. Let’s import that module into the index.tsx file and use it.
import {NextPage} from "next";
import React, {lazy, Suspense, useState} from "react";
import dynamic from 'next/dynamic'


const InsuranceDetail2 = dynamic(() => import(('InsuranceA/InsuranceDetail')), {
    ssr: false
}) as NextPage;

const Insurance: NextPage = ({}: any) => {

    return (

            <InsuranceDetail2 />

    )
}


export default Insurance

After doing above steps I was able to see that remote js file is being loaded in browser's network tab but remote component is not getting rendered and getting blank page.

Please find attached screenshot

Please let me know if I am missing anything here. I took the reference from below links.

  1. https://blog.logrocket.com/micro-frontend-react-next-js/
  2. https://blog.logrocket.com/building-micro-frontends-webpacks-module-federation/
  3. https://dev.to/omher/building-react-app-with-module-federation-and-nextjsreact-1pkh

Upvotes: 3

Views: 9160

Answers (2)

Joey Gough
Joey Gough

Reputation: 3103

This is a rapidly changing package so information becomes outdated quickly and is very specific to the version you are using. But after a quick glance at your config, here are some things that I would suggest. I am assuming you are using version 6 of the nextjs-mf package.

  1. I don't think that you need react in the shared config.
  2. remoteType: "var" is unneccessary
  3. set filename to be "static/chunks/remoteEntry.js"
  4. you are probably going to want to set automaticAsyncBoundary: true in the extra options

If you are getting the remote entry file in the host, then that means your network config is more or less configured ok. If the components are not rendering, you can try to inspect the remote entry file for the component or module that you are exposing. see if it exists in there. Then if it is there you should make sure that the format is correct (esm or commonjs). if you are wiring up two nextjs apps it should work with minimal intervention, so remove the remote type setting. just let the plugin do that.

configure your remotes like this:

remotes: {
  remote: `remote@http://localhost:8080/_next/static/${
    isServer ? 'ssr' : 'chunks'
  }/remoteEntry.js`,
},

Upvotes: 0

Bhavik Shah
Bhavik Shah

Reputation: 19

Installing nextjs-mf ⚠️ Attention: for the application to work with Module Federation features you need to have access to the https://app.privjs.com/package?pkg=@module-federation/nextjs-mf[[nextjs-ssr^] plugin which currently requires a paid license!

To install the tool, we need to login to [PrivJs}(https://privjs.com/^) using npm, to do so, run the following command:

npm login --registry https://r.privjs.com

Once this is done a file containing your credentials will be saved in ~/.npmrc. Now you can install nextjs-mf using the command below:

npm install @module-federation/nextjs-mf --registry https://r.privjs.com

so module federation is paid module in next js and with the help of paid module, I am able to achieve it.

  1. next.config.js of insurance module.
/** @type {import('next').NextConfig} */
const NextFederationPlugin = require('@module-federation/nextjs-mf');
const assetPrefix = '/jobs-assets';
const nextConfig = {
  assetPrefix,
  env: {
    assetPrefix
  },
  reactStrictMode: true,
  webpack5: true,
  srcDir: 'src/node/',
  //distDir: 'build',
  webpack: (config, options) => { // webpack configurations
      if (!options.isServer) {
          config.plugins.push(
              new NextFederationPlugin({
                  name: "insurancea",
                  filename: "static/chunks/pages/cm_insurance_web.js", // remote file name which will used later
                  exposes: { // expose all component here.
                      "./insurancedetail": "./components/Insurance_Details.tsx"
                  },
                  shared:
                      {
                          react: {
                              singleton: true,
                              requiredVersion: false,
                          }
                      }
              }),
          );
      }
      return config
  }
};

module.exports = nextConfig

  1. add dependency of module federation in package.json.
"dependencies": {

 "@module-federation/nextjs-mf": "^5.9.2",
}
  1. add import in _app.tsx file of same insurance module.
import '@module-federation/nextjs-mf/src/include-defaults';

That's it for expose component

  1. Now update it for remote component (consumer app - cm-job-board-web) in next.config.js
/** @type {import('next').NextConfig} */
const NextFederationPlugin = require('@module-federation/nextjs-mf');
const assetPrefix = '/jobs-assets';
const path = require('path');
const nextConfig = {
  assetPrefix,
  env: {
    assetPrefix
  },
  basePath: '/search-jobs',
  reactStrictMode: true,
  srcDir: 'src/node/',
  webpack: (config, options) => {
      if (!options.isServer) {
          config.plugins.push(
              new NextFederationPlugin({
                  name: "jobboardWeb",
                  filename: "static/chunks/cm_job_board_web.js",
                  remotes: {
                      //  cm_insurance_web: options.isServer ? 'http://localhost:3000/jobs-assets/_next/static/chunks/cm_insurance_web.js' : 'fe1'
                      insurancea: 'insurancea@http://localhost:3000/jobs-assets/_next/static/chunks/pages/cm_insurance_web.js'
                  }, exposes: {},
                  shared: {}
              }),
          );
      }
    return config
  },
  webpack5: true
};

module.exports = nextConfig

  1. add dependency of module federation in package.json of consumer app.
"dependencies": {

 "@module-federation/nextjs-mf": "^5.9.2",
}
  1. add import in _app.tsx file of consumer app.
import '@module-federation/nextjs-mf/src/include-defaults';
  1. finally import that module into the index.tsx file and use it in consumer app.
import { Suspense } from 'react'
import React from 'react'
import dynamic from 'next/dynamic'

const DynamicComponent4 = dynamic(
    () => import('insurancea/insurancedetail'),
    { loading: () => <p>Loading caused by client page transition ...</p>, ssr: false }
)


export default function Insurance() {
    return (
        <div>

                <DynamicComponent4 />

        </div>
    )
}

That's it.

Upvotes: -6

Related Questions