This plugin enables Module Federation on Next.js
This is a workaround to hard limitations caused by Next.js being synchronous.
I am working on an update to Webpack Core which will circumvent projects with older architecture (like Next.js).
This is a stable and viable workaround to leverage Module Federation until this issue is resolved.
- next ^9.5.6
- SSG
- SSR
Once I PR webpack, this workaround will no longer be required.
You can see it in action here: https://github.com/module-federation/module-federation-examples/tree/master/nextjs
yarn global add @module-federation/nextjs-mf
Run this inside of a fresh nextjs install.
nextjs-mf upgrade -p 3001
- Use
withModuleFederation
in yournext.config.js
// next.config.js
const {
withModuleFederation,
} = require("@module-federation/nextjs-mf");
const path = require("path");
module.exports = {
webpack: (config, options) => {
const { buildId, dev, isServer, defaultLoaders, webpack } = options;
const mfConf = {
mergeRuntime: true, //this is experimental, read below
name: "next2",
library: { type: config.output.libraryTarget, name: "next2" },
filename: "static/runtime/remoteEntry.js",
remotes: {
// For SSR, resolve to disk path (or you can use code streaming if you have access)
next1: isServer
? path.resolve(
__dirname,
"../next1/.next/server/static/runtime/remoteEntry.js"
)
: "next1", // for client, treat it as a global
},
exposes: {
"./nav": "./components/nav",
},
shared: ["lodash"],
};
// Configures ModuleFederation and other Webpack properties
withModuleFederation(config, options, mfConf);
if (!isServer) {
config.output.publicPath = "http://localhost:3001/_next/";
}
return config;
},
};
- Add the
patchSharing
to_document.js
. This will solve the react sharing issue.
import Document, { Html, Head, Main, NextScript } from "next/document";
import { patchSharing } from "@module-federation/nextjs-mf";
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
render() {
return (
<Html>
{patchSharing()}
<script src="http://localhost:3000/_next/static/chunks/webpack.js" />
<script src="http://localhost:3000/_next/static/runtime/remoteEntry.js" />
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
- Use top-level-await
// some-component.js
const Nav = (await import("../components/nav")).default;
const _ = await import("lodash");
Use at your own risk.
Next.js uses runtimeChunk:'single'
Which forces us to also add the webpack script itself. Till this is fixed in webpack, heres a plugin that will merge the runtimes back together for MF
This can be enabled via mergeRuntime
flag. This is not part of Module Federation, its part of this plugin.
withModuleFederation(config, options, {mergeRuntime:true,...mfConf})
You can manually add it as follows
const {MergeRuntime} = require("@module-federation/nextjs-mf");
// in your next config.
config.plugins.push(new MergeRuntime({filename: 'remoteEntry'}));
This allows the following to be done
- <script src="http://localhost:3000/_next/static/chunks/webpack.js" />
- <script src="http://localhost:3000/_next/static/runtime/remoteEntry.js" />
+ <script src="http://localhost:3000/_next/static/remoteEntryMerged.js" />