I followed below steps to achieve module federation in next js.
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
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.
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
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;
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.
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.
/** @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
"dependencies": {
"@module-federation/nextjs-mf": "^5.9.2",
}
import '@module-federation/nextjs-mf/src/include-defaults';
That's it for expose component
/** @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
"dependencies": {
"@module-federation/nextjs-mf": "^5.9.2",
}
import '@module-federation/nextjs-mf/src/include-defaults';
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.