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 yournext.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,
});
⬆️ Configuring Remote App - React
- Use
ModuleFederationPlugin
in yourwebpack.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 ofwebpack.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: {
},
}),
⬇️ Configuring Host App Host - React
- Use
ModuleFederationPlugin
in yourwebpack.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 ofwebpack.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',
}),
],
-
📝 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
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;
🎉 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
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 />);
- 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');
-
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 />);
- More in here
- Uncaught (in promise) TypeError: Cannot read properties of null (reading 'parentNode')
Solution
- By default
NextJS
adds a meta tag in itsHTML
called:__next_css__DO_NOT_USE__
to theirHTML
files We need that tag on our non next apps so the injector can find and load
css
below that tagindex.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>
- Getting 404 for remotes Components
Solution
-
webpack
thinks public path is/
which is wrong. You want it to calculate the path based ondocument.currentScript.src
- Set
publicPath:auto
in yourwebpack.config.js
- Not adding all the configuration of
webpack.config.js
of the app
output: {
publicPath: 'auto',
},
🔗 Resources
- Github repo link
- App
- Host: Link for React App Hosted at Vercel
- Remote: For NextJS App Hosted at Vercel
- Remote App: For React App Hosted at Vercel
- Module Federation Examples
Top comments (5)
Hi, thank you for the article. The links are not working.
Thanks,
Links are working now
Thanks for your input
Thanks, is there any other solution for module-federation/nextjs-mf?
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.
Looks like this plugin is free now npmjs.com/package/@module-federati...