Skip to content

Instantly share code, notes, and snippets.

@peter-gorgenyi-mgm
Created June 23, 2021 22:31
Show Gist options
  • Save peter-gorgenyi-mgm/410cdd60495cce39f31365c509e7ec5f to your computer and use it in GitHub Desktop.
Save peter-gorgenyi-mgm/410cdd60495cce39f31365c509e7ec5f to your computer and use it in GitHub Desktop.
README update suggestion for nextjs-mf

Module Federation For Next.js

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.

Supports

  • next ^10.2.x
  • Client side only

Once I PR webpack, this workaround will no longer be required.

Check out our book

Practical Module Federation Book We will be actively updating this book over the next year as we learn more about best practices and what issues people are running into with Module Federation, as well as with every release of Webpack as it moves towards a release candidate and release. So with your one purchase you are buying a whole year of updates.

Demo

You can see it in action here: https://github.com/module-federation/module-federation-examples/tree/master/nextjs (needs to be updated)

Prequisites

The current version supports NextJS 10.2. If you start with a new NextJS project (11.0 or newer) you need to downgrade to 10.2 by specifying the version in your package.json for both "next1" and "next2".

"dependencies": {
  ...
  "next": "10.2.3",
  ...
},

Then you need to remove your node_modules folder and reinstall dependencies:

$ cd next1
$ rm -rf node_modules/
$ yarn

Finally install the plugin as a dependency:

$ yarn add @module-federation/nextjs-mf

How to add a sidecar for exposes to your nextjs app

  1. Use withFederatedSidecar in your next.config.js of 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,
    }
  },
})({
  future: {
    webpack5: true
  },
  // your original next.config.js export
});
  1. 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: {
          react: {
            // Notice shared ARE eager here.
            eager: true,
            singleton: true,
            requiredVersion: false,
          }
        },
      })
    );

    return config;
  },
};
  1. Make sure you have an _app.js file, 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",
});

Note that your next.config.js for "next1" should look similar to below:

const {
  withFederatedSidecar,
  federationLoader,
} = require("@module-federation/nextjs-mf");
const deps = require("./package.json").dependencies;
module.exports = withFederatedSidecar({
  name: "next1",
  filename: "static/chunks/remoteEntry.js",
  exposes: {},
  shared: {
    react: {
      // Notice shared are NOT eager here.
      requiredVersion: false,
      singleton: true,
    },
  },
})({
  // your original next.config.js export
  future: {
    webpack5: true,
  },
  webpack(config, options) {
    const { webpack } = options;
    config.experiments = { topLevelAwait: true };
    config.module.rules.push({
      test: /_app.js/,
      loader: "@module-federation/nextjs-mf/lib/federation-loader.js",
    });
    config.plugins.push(
      new webpack.container.ModuleFederationPlugin({
        remoteType: "var",
        remotes: {
          next2: "next2",
        },
        shared: {
          react: {
            // Notice shared ARE eager here.
            eager: true,
            singleton: true,
            requiredVersion: false,
          },
        },
      })
    );

    return config;
  },
});
  1. Add the remote entry for "next2" to the pages/_document.js 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;
  1. Use next/dynamic to import from your remotes
import dynamic from "next/dynamic";
...
const SampleComponent = dynamic(() => import("next2/sampleComponent"), {
  ssr: false,
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment