Reputation: 19
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.
Upvotes: 3
Views: 9160
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.
remoteType: "var"
is unneccessary"static/chunks/remoteEntry.js"
automaticAsyncBoundary: true
in the extra optionsIf 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
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.
/** @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.
Upvotes: -6