README
Module Federation For Next.js
This plugin enables Module Federation on Next.js
This is a stable and viable solution to leverage Module Federation until this issue is resolved.
Supports
- next ^10.2.x || ^11.x.x
- Client side only
Whats shared by default?
Under the hood we share some next internals automatically You do not need to share these packages, sharing next internals yourself will cause errors.
"next/dynamic": {
requiredVersion: false,
singleton: true,
},
"next/link": {
requiredVersion: false,
singleton: true,
},
"next/head": {
requiredVersion: false,
singleton: true,
},
Things to watch out for
There's a big in next.js which causes it to attempt and fail to resolve federated imports on files imported into the pages/index.js
Its recommended using the low-level api to be safe.
const SampleComponent = dynamic(
() => window.next2.get("./sampleComponent").then((factory) => factory()),
{
ssr: false,
}
);
Make sure you are using mini-css-extract-plugin@2 - version 2 supports resolving assets through publicPath:'auto'
Options
withFederatedSidecar(
{
name: "next2",
filename: "static/chunks/remoteEntry.js",
exposes: {
"./sampleComponent": "./components/sampleComponent.js",
},
shared: {
react: {
// Notice shared are NOT eager here.
requiredVersion: false,
singleton: true,
},
},
},
{
removePlugins: [
// optional
// these are the defaults
"BuildManifestPlugin",
"ReactLoadablePlugin",
"DropClientPage",
"WellKnownErrorsPlugin",
"ModuleFederationPlugin",
],
publicPath: "auto", // defaults to 'auto', is optional
}
);
Demo
You can see it in action here: https://github.com/module-federation/module-federation-examples/tree/master/nextjs
How to add a sidecar for exposes to your nextjs app
- Use
withFederatedSidecarin yournext.config.jsof the app that you wish to expose modules from. We'll call this "next2".
// next.config.js
const { withFederatedSidecar } = require("@module-federation/nextjs-mf");
module.exports = withFederatedSidecar({
name: "next2",
filename: "static/chunks/remoteEntry.js",
exposes: {
"./sampleComponent": "./components/sampleComponent.js",
},
shared: {
react: {
// Notice shared are NOT eager here.
requiredVersion: false,
singleton: true,
},
},
})({
// your original next.config.js export
});
- For the consuming application, we'll call it "next1", add an instance of the ModuleFederationPlugin to your webpack config:
module.exports = {
webpack(config) {
config.plugins.push(
new options.webpack.container.ModuleFederationPlugin({
remoteType: "var",
remotes: {
next2: "next2",
},
shared: {
// we have to share something to ensure share scope is initialized
"@module-federation/nextjs-mf/lib/noop": {
eager: false,
},
},
})
);
return config;
},
};
- Make sure you have an
_app.jsfile, then add the loader
// we attach next internals to share scope at runtime
config.module.rules.push({
test: /_app.js/,
loader: "@module-federation/nextjs-mf/lib/federation-loader.js",
});
- Add the remote entry for "next2" to the _document for "next1"
import Document, { Html, Head, Main, NextScript } from "next/document";
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
render() {
return (
<Html>
<Head />
<body>
<Main />
<script src="http://next2-domain-here.com/_next/static/chunks/remoteEntry.js" />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
- Use next/dynamic to import from your remotes
const SampleComponent = dynamic(
() => window.next2.get("./sampleComponent").then((factory) => factory()),
{
ssr: false,
}
);
Contact
If you have any questions or need to report a bug Reach me on Twitter @ScriptedAlchemy
Or join this discussion thread: https://github.com/module-federation/module-federation-examples/discussions/978