Building React App with Module Federation and NextJS/React
Omher
Posted on May 23, 2022
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
Posted on May 23, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.