Understand webpack Federation for Micro Frontend

Next.js + Module Federation

https://github.com/tkssharma/nextjs-micro-frontends

Module Federation

Module Federation is a Webpack 5 feature that has arrived to make it possible to share parts of an application to another at runtime. This makes it possible for multiple applications compiled with webpack to repurpose parts of their code as the user interacts with them, which takes us to the next step.

Next.js + Module Federation

Let’s start with our first example of this article where we talk about an eCommerce application, now imagine that our marketing team decides to create a mega Black Friday campaign and decides to change several parts of our application by inserting different components with dynamic banners, carousels, countdowns, themed offers, etc… this would probably be a headache for all teams responsible for our microfrontend applications since each one would have to implement the new requirements of the marketing team in their projects and that would have to be very well tested and synchronized so that everything went right and nothing could be released ahead of time… Anyway, all this could easily generate a lot of work and a lot of headache for the team, but that’s where the very powerful Module Federation comes in.

Thanks to it, only one team would be in responsible for developing the new components along with their respective logic, and the rest of the team would only be responsible for implementing the use of these new complements, which could bring with them, hooks, components in React, among others.

Unfortunately, implementing and using the Module Federation features of Webpack with Next.js is not that easy, as you would need to deeply understand how both tools work to be able to create a solution that facilitates the integration between the two. Fortunately, there is already a solution and has several features including support for SSR (server-side rendering), these tools are called nextjs-mf and nextjs-ssr and together we are going to explore a proof-of-concept application that I created to show you the power of these tools together.

⚠️ Attention: for the application to work with Module Federation features you need to have access to the nextjs-mf or nextjs-ssr plugin which currently requires a paid license!

Let’s start with our first example of this article where we talk about an eCommerce application, now imagine that our marketing team decides to create a mega Black Friday campaign and decides to change several parts of our application by inserting different components with dynamic banners, carousels, countdowns, themed offers, etc… this would probably be a headache for all teams responsible for our microfrontend applications since each one would have to implement the new requirements of the marketing team in their projects and that would have to be very well tested and synchronized so that everything went right and nothing could be released ahead of time… Anyway, all this could easily generate a lot of work and a lot of headache for the team, but that’s where the very powerful Module Federation comes in.

Thanks to it, only one team would be in responsible for developing the new components along with their respective logic, and the rest of the team would only be responsible for implementing the use of these new complements, which could bring with them, hooks, components in React, among others.

Unfortunately, implementing and using the Module Federation features of Webpack with Next.js is not that easy, as you would need to deeply understand how both tools work to be able to create a solution that facilitates the integration between the two. Fortunately, there is already a solution and has several features including support for SSR (server-side rendering), these tools are called nextjs-mf and nextjs-ssr and together we are going to explore a proof-of-concept application that I created to show you the power of these tools together.

⚠️ Attention: for the application to work with Module Federation features you need to have access to the nextjs-mf or nextjs-ssr plugin which currently requires a paid license!

Microfrontends using Next.js and Module Federation

Microfrontends using Next.js and Module Federation

https://www.npmjs.com/package/@module-federation/nextjs-mf

https://github.com/tkssharma/nextjs-micro-frontends

how to expose components from Guest application

import Image from 'next/image'
import styles from '../styles/Mario.module.css'

const Mario = () => {
  return (
    <main className={styles.main}>
      <Image
        src="https://upload.wikimedia.org/wikipedia/en/a/a9/MarioNSMBUDeluxe.png"
        alt="Mario"
        width={240}
        height={413}
      />
      <h1 className={styles.title}>
        G'day! I'm Mario, a microfrontend.
      </h1>
      <span>I'm hosted at <a target="_blank" href="https://mf-micro-front-end-activate.vercel.app">https://mf-micro-front-end-activate.vercel.app</a></span>
    </main>
  )
}

export default Mario

Here in next.config we are exposing components mario from this application, same we can do from another front end application and render both on different routes on container Host application

  webpack: (config, options) => {
    const { isServer } = options;
    const mfConf = {
      mergeRuntime: true, //experimental
      name: "app2",
      library: {
        type: config.output.libraryTarget,
        name: "app2",
      },
      filename: "static/runtime/app2remoteEntry.js",
      remotes: {
      },
      exposes: {
        "./mario": "./components/mario",
      },
    };

Micro Front end 01

We are building a simple application which will render componnets from two different other front end application,

  • we have one Host application (container app)
  • we have guest application from which we will render Mario component
  • we have another application from which we will render another components

poki1 poki2 poki3

const {
  withModuleFederation,
} = require("@module-federation/nextjs-mf");
module.exports = {
  future: { webpack5: true },
  images: {
    domains: ['upload.wikimedia.org'],
  },
  webpack: (config, options) => {
    const { isServer } = options;
    const mfConf = {
      mergeRuntime: true, //experimental
      name: "app2",
      library: {
        type: config.output.libraryTarget,
        name: "app2",
      },
      filename: "static/runtime/app2remoteEntry.js",
      remotes: {
      },
      exposes: {
        "./mario": "./components/mario",
      },
    };
    config.cache = false;
    withModuleFederation(config, options, mfConf);

    return config;
  },

Micro Front end 02

const {
  withModuleFederation,
} = require("@module-federation/nextjs-mf");
module.exports = {
  future: { webpack5: true },
  images: {
    domains: ['upload.wikimedia.org'],
  },
  webpack: (config, options) => {
    const { isServer } = options;
    const mfConf = {
      mergeRuntime: true, //experimental
      name: "app1",
      library: {
        type: config.output.libraryTarget,
        name: "app1",
      },
      filename: "static/runtime/app1RemoteEntry.js",
      remotes: {
      },
      exposes: {
        "./luigi": "./components/luigi",
      },
    };
    config.cache = false;
    withModuleFederation(config, options, mfConf);

    return config;
  },

Shell Container Micro Front end

const {
withModuleFederation,
} = require("@module-federation/nextjs-mf");

module.exports = {
future: { webpack5: true },
images: {
  domains: ['upload.wikimedia.org'],
},
webpack: (config, options) => {
  const mfConf = {
    name: "shell",
    library: {
      type: config.output.libraryTarget,
      name: "shell",
    },
    remotes: {
      app1: "app1",
      app2: "app2",
    },
    exposes: {
    },
  };
  config.cache = false;
  withModuleFederation(config, options, mfConf);

  return config;
},

Container app page route

import dynamic from 'next/dynamic'

const RemoteMario = dynamic(
() => import('app2/mario'),
{ ssr: false }
)

const App1 = () => (<RemoteMario />)

export default App1

we have to import bundles from other applications in _document.js

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>
        <script src="http://localhost:3002/_next/static/runtime/app1RemoteEntry.js" />
        <script src="http://localhost:3001/_next/static/runtime/app2RemoteEntry.js" />
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

How to Test setup

cd micro-front-end-activate
npm install 
nbpm run build
npm run dev


cd micro-front-end-main
npm install
npm run build
npm run dev


cd micro-front-end-shell
npm install 
npm run build 
npm run dev 

Testing setup

  • localhost:3000/maruo will render mario component from app2
  • localhost:3000/luigi will render luigi component from app1

Comments