Masterbook/Chapter 78• 4 min read
View Raw Code on GitHub

What is module federation in modern JS apps?

Module Federation in Modern JavaScript Applications

As web applications become larger and teams more distributed, breaking your app into independently deployable pieces—often called micro-frontends—helps manage complexity. Sharing code between these pieces can be challenging. Module Federation, introduced in webpack 5, allows multiple builds to share code and load modules from each other at runtime.

Core concepts

At its heart, module federation enables one application (the host) to consume modules exposed by another application (the remote). Both host and remote are separate builds with their own dependency graphs. Instead of bundling shared code into the host, the host dynamically loads modules from the remote when needed.

Host application

The host defines which remote applications it depends on via the ModuleFederationPlugin in its webpack configuration:

// webpack.config.js in host
const { ModuleFederationPlugin } = require("webpack").container;
module.exports = {
    plugins: [
        new ModuleFederationPlugin({
            name: "host",
            remotes: {
                app2: "app2@http://localhost:3002/remoteEntry.js",
            },
        }),
        shared: { 
            react: { 
                singleton: true 
            }, 
            "react-dom": { 
                singleton: true 
            } 
        },
    ]
};

Here app2 is a remote application available at the given URL. The shared section ensures both host and remote use the same instance of shared libraries like React.

Remote application

The remote app exposes modules via its own ModuleFederationPlugin configuration:

// webpack.config.js in remote
module.exports = {
    plugins: [
        new ModuleFederationPlugin({
            name: "app2",
            filename: "remoteEntry.js",
            exposes: {
                "./Button": "./src/components/Button.js",
            },
        }),
        shared: { 
            react: { 
                singleton: true
            }, 
            "react-dom": {
                singleton: true 
            }
        },
    ],
};

This configuration exposes a Button component located at ./src/components/Button.js. The filename option (remoteEntry.js) is the file the host will load to discover the exposed modules.

Consuming a remote module

In the host application, import the remote module using a special syntax understood by webpack:

// In a React component inside the host
import React, { Suspense } from "react";
const RemoteButton = React.lazy(() => import("app2/Button"));
export default function Home() {
    return (
        <div>
            <h1>Welcome to the host app!</h1>
            <Suspense fallback={<div>Loading button...</div>}>
                <RemoteButton />
            </Suspense>
        </div>
    );
}

When the RemoteButton component is rendered, webpack fetches remoteEntry.js from the remote and loads the Button module on demand. Because both apps share React as a singleton, there are no version conflicts.

Advantages of module federation

  • Independent deployment - Remotes can be updated or deployed without redeploying the host. Apps can evolve at their own pace.
  • Code sharing - Common libraries and components are shared rather than duplicated, reducing bundle sizes and improving consistency.
  • Runtime integration - Modules are loaded when needed, enabling dynamic features or experiments without full rebuilds.
  • Micro-frontend architecture - Teams can build and own separate parts of a larger application, coordinating via well-defined interfaces.

Considerations and challenges

  • Version compatibility - Shared dependencies must be compatible across host and remotes. Singleton configuration helps ensure only one version is used.
  • Complex setup - Correctly configuring hosts, remotes and shared libraries requires careful planning. Tools like @module-federation/nextjs-mf or @module-federation/vite simplify integration with popular frameworks.
  • Network latency - Loading remote modules introduces network requests. Use caching and prefetching to mitigate latency.
  • Security - Loading code from another domain means you must trust the remote application. Implement proper content security policies and version controls.

Analogy

Imagine your application as a city. Some districts are built by different teams (remote apps). Instead of duplicating common resources like power plants (libraries) in every district, the city builds shared infrastructure. When a district needs a new service (a module), it requests it from the central provider and plugs it in without reconstructing the whole district.

Practice questions

Conceptual questions

  1. What problem does module federation solve in large applications? Describe the roles of the host and the remote.
  2. In the webpack configuration for the host, what does the remotes property specify? What is the purpose of the shared property?
  3. How does a remote application expose a module? What is the filename option used for?
  4. Explain the advantages and potential challenges of using module federation.
  5. How could module federation support a micro-frontend architecture?

Coding exercises

  1. Set up a minimal host and remote using webpack 5. Expose a component from the remote and import it in the host. Ensure that both share React as a singleton.
  2. Modify the host so that it loads the remote component lazily using React.lazy and displays a fallback while loading.
  3. Experiment with sharing a utility library (e.g., Lodash) between the host and remote. Observe how changing the version in one app affects the other.
  4. Design a simple micro-frontend dashboard where each widget (e.g., weather, news, stock) is built as a separate remote. Use module federation to assemble the widgets in the host at runtime.