App Router Not Supported
# Next.js :::danger Project Deprecation Support for Next.js is ending [read more](https://github.com/module-federation/core/issues/3153) ::: This plugin enables Module Federation on Next.js ## Supports - next ^15 || ^14 || ^13 || ^12 - Server-Side Rendering - Pages router I highly recommend referencing this application which takes advantage of the best capabilities: [https://github.com/module-federation/module-federation-examples](https://github.com/module-federation/module-federation-examples) ## Requirement I set `process.env.NEXT_PRIVATE_LOCAL_WEBPACK = 'true'` inside this plugin, but its best if its set in env or command line export. "Local Webpack" means you must have webpack installed as a dependency, and next will not use its bundled copy of webpack which cannot be used as i need access to all of webpack internals - `cross-env NEXT_PRIVATE_LOCAL_WEBPACK=true next dev` or `next build` - `npm install webpack` ## Usage ```js import React, { lazy } from 'react'; const SampleComponent = lazy(() => import('next2/sampleComponent')); ``` To avoid hydration errors, use `React.lazy` instead of `next/dynamic` for lazy loading federated components. #### See the implementation here: [https://github.com/module-federation/module-federation-examples/tree/master/nextjs-v13/home/pages](https://github.com/module-federation/module-federation-examples/tree/master/nextjs-v13/home/pages) With async boundary installed at the page level. You can then do the following ```js const SomeHook = require('next2/someHook'); import SomeComponent from 'next2/someComponent'; ``` ## Demo You can see it in action here: [https://github.com/module-federation/module-federation-examples/tree/master/nextjs-ssr](https://github.com/module-federation/module-federation-examples/tree/master/nextjs-ssr) ## Options This plugin works exactly like ModuleFederationPlugin, use it as you'd normally. Note that we already share react and next stuff for you automatically. Also NextFederationPlugin has own optional argument `extraOptions` where you can unlock additional features of this plugin: ```js new NextFederationPlugin({ name: '', filename: '', remotes: {}, exposes: {}, shared: {}, extraOptions: { debug: boolean, // `false` by default exposePages: boolean, // `false` by default enableImageLoaderFix: boolean, // `false` by default enableUrlLoaderFix: boolean, // `false` by default skipSharingNextInternals: boolean, // `false` by default }, }); ``` - `debug` – enables debug mode. It will print additional information about what is going on under the hood. - `exposePages` – exposes automatically all nextjs pages for you and theirs `./pages-map`. - `enableImageLoaderFix` – adds public hostname to all assets bundled by `nextjs-image-loader`. So if you serve remoteEntry from `http://example.com` then all bundled assets will get this hostname in runtime. It's something like Base URL in HTML but for federated modules. - `enableUrlLoaderFix` – adds public hostname to all assets bundled by `url-loader`. - `skipSharingNextInternals` – disables sharing of next internals. You can use it if you want to share next internals yourself or want to use this plugin on non next applications ## Demo You can see it in action here: [https://github.com/module-federation/module-federation-examples/pull/2147](https://github.com/module-federation/module-federation-examples/pull/2147) ## Implementing the Plugin 1. Use `NextFederationPlugin` in your `next.config.js` of the app that you wish to expose modules from. We'll call this "next2". ```js // next.config.js // either from default const NextFederationPlugin = require('@module-federation/nextjs-mf'); module.exports = { webpack(config, options) { const { isServer } = options; config.plugins.push( new NextFederationPlugin({ name: 'next2', remotes: { next1: `next1@http://localhost:3001/_next/static/${ isServer ? 'ssr' : 'chunks' }/remoteEntry.js`, }, filename: 'static/chunks/remoteEntry.js', exposes: { './title': './components/exposedTitle.js', './checkout': './pages/checkout', }, shared: { // whatever else }, }), ); return config; }, }; ``` ```js // next.config.js const NextFederationPlugin = require('@module-federation/nextjs-mf'); module.exports = { webpack(config, options) { const { isServer } = options; config.plugins.push( new NextFederationPlugin({ name: 'next1', remotes: { next2: `next2@http://localhost:3000/_next/static/${ isServer ? 'ssr' : 'chunks' }/remoteEntry.js`, }, }), ); return config; }, }; ``` 4. Use react.lazy, low level api, or require/import from to import remotes. ```js import React, { lazy } from 'react'; const SampleComponent = lazy(() => window.next2.get('./sampleComponent').then((factory) => { return { default: factory() }; }), ); // or const SampleComponent = lazy(() => import('next2/sampleComponent')); //or import Sample from 'next2/sampleComponent'; ``` ## RuntimePlugins To provide extensibility and "middleware" for federation, you can refer to `@module-federation/enhanced/runtime` ```js // next.config.js new NextFederationPlugin({ runtimePlugins: [require.resolve('./path/to/myRuntimePlugin.js')], }); ``` ## Utilities `loadRemote` has been removed - you can take advantage of the new runtime apis: [https://module-federation.io/guide/runtime/runtime-api.html#loadremote](https://module-federation.io/guide/runtime/runtime-api.html#loadremote) **revalidate** Enables hot reloading of node server (not client) in production. This is recommended, without it - servers will not be able to pull remote updates without a full restart. More info here: [https://github.com/module-federation/nextjs-mf/tree/main/packages/node#utilities](https://github.com/module-federation/nextjs-mf/tree/main/packages/node#utilities) ```js // __document.js import { revalidate } from '@module-federation/nextjs-mf/utils'; import Document, { Html, Head, Main, NextScript } from 'next/document'; class MyDocument extends Document { static async getInitialProps(ctx) { const initialProps = await Document.getInitialProps(ctx); // can be any lifecycle or implementation you want ctx?.res?.on('finish', () => { revalidate().then((shouldUpdate) => { console.log('finished sending response', shouldUpdate); }); }); return initialProps; } render() { return ( <Html> <Head /> <body> <Main /> <NextScript /> </body> </Html> ); } } ``` ## For Express.js Hot reloading Express.js required additional steps: [https://github.com/module-federation/core/blob/main/packages/node/README.md](https://github.com/module-federation/core/blob/main/packages/node/README.md) ## 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.
Click to view `DEFAULT_SHARE_SCOPE` : ```ts export const DEFAULT_SHARE_SCOPE: SharedObject = { 'next/dynamic': { requiredVersion: undefined, singleton: true, import: undefined, }, 'next/head': { requiredVersion: undefined, singleton: true, import: undefined, }, 'next/link': { requiredVersion: undefined, singleton: true, import: undefined, }, 'next/router': { requiredVersion: false, singleton: true, import: undefined, }, 'next/image': { requiredVersion: undefined, singleton: true, import: undefined, }, 'next/script': { requiredVersion: undefined, singleton: true, import: undefined, }, react: { singleton: true, requiredVersion: false, import: false, }, 'react/': { singleton: true, requiredVersion: false, import: false, }, 'react-dom/': { singleton: true, requiredVersion: false, import: false, }, 'react-dom': { singleton: true, requiredVersion: false, import: false, }, 'react/jsx-dev-runtime': { singleton: true, requiredVersion: false, }, 'react/jsx-runtime': { singleton: true, requiredVersion: false, }, 'styled-jsx': { singleton: true, import: undefined, version: require('styled-jsx/package.json').version, requiredVersion: '^' + require('styled-jsx/package.json').version, }, 'styled-jsx/style': { singleton: true, import: false, version: require('styled-jsx/package.json').version, requiredVersion: '^' + require('styled-jsx/package.json').version, }, 'styled-jsx/css': { singleton: true, import: undefined, version: require('styled-jsx/package.json').version, requiredVersion: '^' + require('styled-jsx/package.json').version, }, }; ```