Module Federation in JavaScript
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, since 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. This entry point contains a small runtime that registers the remote’s exposed modules and negotiates shared dependencies. Once published, the remote does not need to know anything about which hosts are consuming it. The host configuration is equally straightforward.
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
With both the remote and host configured, the host can import remote modules as if they were part of the local build. Webpack resolves the import at runtime rather than at build time, which means the host never bundles the remote’s source code. 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:
- The host’s runtime loads
remoteEntry.jsfrom the remote - The host asks the container for
remote/Button - The remote’s container serves the module
- 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, since 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, giving every consuming application the same version without a rebuild. Applications consume it without bundling it. When the library updates, consumers pick up the change on the next page load:
// 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. The plugin declares its entry point as an exposed module, while the host provides API surfaces through shared dependencies. This avoids the need for a custom plugin registry:
// 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.
Draw the boundary first
Module Federation works best when each boundary has a clear owner. Before you expose anything, decide which features belong in the host and which belong in the remote. If the split is vague, the runtime loading model becomes harder to reason about and the code review process gets muddy. A clean boundary makes it obvious which team ships what, which bundle owns which dependency, and where a bug should be fixed. That clarity matters more than the number of exposed modules.
Watch shared dependencies
Shared packages save bytes only when the versions line up and the loading order stays predictable. If a dependency is shared too aggressively, a small version drift can affect several apps at once. Keep the shared list short and deliberate. Start with the libraries that truly need singletons, then add more only when duplication causes a measurable cost. That way, the host and the remote stay coupled in the few places that matter rather than across the whole bundle graph.
Plan for remote failure
Every remote load is a network request, so failures are normal rather than exceptional. Design the host to show a fallback, retry if needed, and continue functioning when a remote is unavailable. That means treating remote components like optional collaborators instead of hard dependencies for the whole page. The earlier you define that behavior, the easier it is to test. A good fallback path reduces panic during deployment because a bad remote does not have to take down the entire host.
Coordinate Releases
Federated builds still need release discipline. If one remote changes its exposed module shape, the host needs a matching update or a compatibility layer. Teams that work independently should still agree on names, version notes, and rollout timing. A lightweight contract document can prevent a lot of churn. It does not need to be formal, but it should say what is exposed, what is shared, and how a breaking change will be announced. That small habit pays off quickly in multi-team projects.
Document the Contract
Even a small federated setup benefits from a short written contract. The host should know what the remote exposes, which shared packages are expected to stay aligned, and what a breaking change looks like. That note does not have to be formal. It just needs to answer the questions that matter during deployment and review. When the contract is easy to find, the architecture feels less experimental and more like a system the team can operate with confidence.
See Also
- /guides/javascript-modules-esm/, the JavaScript module system (ESM and CommonJS) that Module Federation builds on
- /guides/javascript-pwa-guide/, a related architecture topic: loading application fragments at runtime
- /guides/javascript-web-workers/, another way to load code in isolated contexts