Module Federation in JavaScript

· 5 min read · Updated April 19, 2026 · intermediate
javascript webpack micro-frontend module-federation

Overview

Module Federation is a Webpack 5 feature that lets you split a JavaScript application into separate bundles that share code at runtime. One bundle can load modules from another bundle on demand, without the two having to be built together. Dependencies that both bundles use — like React or Lodash — can be shared automatically so they only load once.

Before Module Federation, sharing code between separate applications meant bundling everything together or using iframe-based micro-frontends. Module Federation gives you a runtime alternative: applications reference each other’s modules as if they were local, and Webpack handles the loading.

Host and Remote

Module Federation works with two roles:

  • Remote: an application that exposes some of its modules to other applications
  • Host: an application that consumes those exposed modules at runtime

The same application can be both a host and a remote — it exposes its own components while consuming modules from other applications.

Basic Setup

Exposing modules from the remote

In your webpack config, use ModuleFederationPlugin to expose components:

// webpack.config.js (Remote App)
const { ModuleFederationPlugin } = require('webpack').container;
const path = require('path');

module.exports = {
  mode: 'development',
  devServer: {
    port: 3001,
  },
  output: {
    publicPath: 'http://localhost:3001/',
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'remote',
      filename: 'remoteEntry.js',
      exposes: {
        './Button': './src/Button',
        './Header': './src/Header',
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true },
      },
    }),
  ],
};

remoteEntry.js is the file the host loads to access the remote’s modules.

Consuming remote modules in the host

// webpack.config.js (Host App)
const { ModuleFederationPlugin } = require('webpack').container;
const path = require('path');

module.exports = {
  mode: 'development',
  devServer: {
    port: 3000,
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'host',
      remotes: {
        remote: 'remote@http://localhost:3001/remoteEntry.js',
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true },
      },
    }),
  ],
};

Loading a remote component

In the host application, import and use the remote component as if it were local:

import React, { Suspense } from 'react';

const Button = React.lazy(() => import('remote/Button'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Button onClick={() => console.log('clicked')}>Click me</Button>
    </Suspense>
  );
}

The import string 'remote/Button' tells Webpack to load the Button module from the remote application at runtime. The Suspense boundary handles the async loading.

How the Loading Works

When the host builds, it does not bundle the remote’s code. Instead, Webpack records the remote’s location in the bundle. At runtime, when the host needs remote/Button:

  1. The host’s runtime loads remoteEntry.js from the remote
  2. The host asks the container for remote/Button
  3. The remote’s container serves the module
  4. If the module uses shared dependencies (like react), the host supplies them from its own copy

This is why shared: { react: { singleton: true } } matters — it prevents two copies of React from loading. Without singleton, each app would load its own copy.

Shared Dependencies

The shared option controls which dependencies are shared between host and remote:

new ModuleFederationPlugin({
  name: 'host',
  remotes: {
    remote: 'remote@http://localhost:3001/remoteEntry.js',
  },
  shared: {
    // Singleton: only one copy loads, regardless of which app loads first
    react: { singleton: true },
    'react-dom': { singleton: true },

    // Non-singleton: each app gets its own copy
    lodash: { singleton: false },

    // With version constraints: use this version if available, fall back otherwise
    axios: { requiredVersion: '^1.0.0' },
  },
});

The singleton behavior is particularly important for React — two copies of the reconciler would cause the DOM to behave incorrectly.

Dynamic Remote Loading

Beyond the build-time import syntax, you can load remote modules dynamically at runtime. This is useful when you do not know which remote to load until the app is running:

// Dynamic remote loader
async function loadRemote(url, scope, module) {
  await __webpack_init_sharing__(scope);

  const container = await import(/* webpackIgnore: true */ url);
  await container.init(__webpack_share_scopes__[scope]);

  const factory = await container.get(module);
  return factory();
}

// Load a module from a remote at runtime
const Button = await loadRemote(
  'http://localhost:3001/remoteEntry.js',
  'default',
  './Button'
);

Button.onClick(); // runs the button's click handler

This pattern lets you configure remote URLs from a server, enabling truly dynamic micro-frontend routing.

Module Federation 2.0

Module Federation has evolved beyond Webpack. The Module Federation 2.0 specification defines a build-tool-agnostic standard for federated modules, now implemented in:

  • Rspack (Rust-based Webpack-compatible bundler)
  • Farm (Rust-based bundler)
  • Vite (via plugins)

The core concepts remain the same, but the underlying loading mechanism no longer depends on Webpack’s runtime.

Common Use Cases

Micro-frontends

Module Federation is most commonly used in micro-frontend architectures. Each team builds and deploys their application independently. The shell (host) application loads the team’s components at runtime:

// Shell app loads Header and Footer from different remotes
const Header = React.lazy(() => import('teamHeader/Header'));
const Footer = React.lazy(() => import('teamFooter/Footer'));

Shared component library

A design system or component library can be deployed as a remote. Applications consume it without bundling it:

// Host app uses a shared component library
import { Button, Modal, Dropdown } from 'designSystem';

Plugin systems

If your application supports plugins, Module Federation provides a standard way for plugins to expose functionality to the host and consume shared services:

// Plugin remote
exposes: {
  './plugin': './src/plugin',
},
shared: {
  hostApi: { import: 'hostApi', shareScope: 'plugin' },
}

Gotchas

Singleton version mismatches. If the host and remote specify incompatible versions of a shared singleton, Module Federation picks the higher version — but the behavior may be unpredictable. Align shared dependency versions across applications.

Build output must be deployed consistently. The host’s publicPath must be configured correctly so that remoteEntry.js and the chunk files are accessible at the expected URLs.

Development vs production. In development with devServer, hot module reloading works across host and remote, but the setup is more fragile than in production. Check that publicPath is set correctly in both modes.

Circular shared dependencies. If remote A shares a module that depends on remote B, and remote B shares a module that depends on A, you can get initialization order problems. Avoid circular dependencies in shared modules.

See Also