|$ curl https://forge-ai.dev/api/markdown?path=docs/build-tools/module-federation
$cat docs/build-tools-—-module-federation.md
updated Recently·14 min read·published

Build Tools — Module Federation

Build ToolsAdvanced
Introduction

Webpack Module Federation (introduced in Webpack 5) is a JavaScript architecture that allows a running application to dynamically load code from another independently deployed application at runtime. It enables true micro-frontend architectures where teams develop, deploy, and version their applications independently while still composing them into a seamless user experience.

Unlike traditional micro-frontend approaches (iframe, server-side composition, build-time integration), Module Federation operates at the module bundler level. Applications share modules directly in the browser with no runtime overhead beyond the actual module loading. This makes it the most performant and flexible micro-frontend solution available today.

What is Module Federation?

Module Federation allows a Webpack build to specify which modules it exposes to other applications and which modules it consumes from other applications at runtime. This creates a distributed module graph where applications share dependencies and components dynamically.

  • Exposes — Modules that your application makes available to other federated applications.
  • Remotes — External applications whose exposed modules your application consumes.
  • Shared — Dependencies that should be shared across federated applications (like React) to avoid duplicate bundles.
  • Host — The application that consumes remote modules and orchestrates the federated experience.

info

Think of Module Federation as "runtime npm imports." Instead of publishing a package to npm, installing it, and rebuilding, you deploy your application and other applications consume its exposed modules directly from your live deployment at runtime.
Core Concepts

Module Federation is built on three core concepts: exposes determine what your application shares, remotes define what other applications you consume, and shared controls how common dependencies are deduplicated across federated boundaries.

webpack.config.js (shell)
JavaScript
1// Module federation plugin configuration
2const { ModuleFederationPlugin } = require("webpack").container;
3
4new ModuleFederationPlugin({
5 // Name of this federated application
6 name: "shell",
7
8 // Modules this application exposes to others
9 exposes: {
10 "./Header": "./src/components/Header",
11 "./Footer": "./src/components/Footer",
12 "./AuthProvider": "./src/auth/AuthProvider",
13 "./Navigation": "./src/components/Navigation",
14 },
15
16 // Remote applications this application consumes
17 remotes: {
18 dashboard: "dashboard@http://localhost:3001/remoteEntry.js",
19 analytics: "analytics@http://localhost:3002/remoteEntry.js",
20 settings: "settings@http://localhost:3003/remoteEntry.js",
21 },
22
23 // Dependencies shared across federated modules
24 shared: {
25 react: {
26 singleton: true, // Only one React instance
27 requiredVersion: "^18.0.0",
28 eager: true, // Load React synchronously
29 },
30 "react-dom": {
31 singleton: true,
32 requiredVersion: "^18.0.0",
33 eager: true,
34 },
35 "react-router-dom": {
36 singleton: true,
37 requiredVersion: "^6.0.0",
38 },
39 },
40});

warning

The singleton: true flag is critical for frameworks like React. Without it, each federated application may load its own React instance, causing duplicate bundles and React hooks errors. Always set singleton: true and requiredVersion for framework dependencies.
Configuration Examples

A complete Module Federation setup involves at least two applications: a host that provides the shell and one or more remotes that provide features. Each remote application needs its own Webpack configuration with the federation plugin.

webpack.config.js (remote + host)
JavaScript
1// Remote application (dashboard) configuration
2// webpack.config.js — dashboard (remote)
3const { ModuleFederationPlugin } = require("webpack").container;
4const deps = require("./package.json").dependencies;
5
6module.exports = {
7 // Must use unique port for each remote
8 devServer: { port: 3001 },
9
10 plugins: [
11 new ModuleFederationPlugin({
12 name: "dashboard",
13 filename: "remoteEntry.js", // Entry point for host to load
14 exposes: {
15 "./DashboardWidget": "./src/DashboardWidget",
16 "./MetricsGrid": "./src/MetricsGrid",
17 "./ChartWidget": "./src/ChartWidget",
18 },
19 shared: {
20 react: {
21 singleton: true,
22 requiredVersion: deps.react,
23 },
24 "react-dom": {
25 singleton: true,
26 requiredVersion: deps["react-dom"],
27 },
28 "recharts": {
29 singleton: true,
30 requiredVersion: deps.recharts,
31 },
32 },
33 }),
34 ],
35};
36
37// Host application — consuming remote modules
38// webpack.config.js — shell (host)
39const { ModuleFederationPlugin } = require("webpack").container;
40
41module.exports = {
42 devServer: { port: 3000 },
43
44 plugins: [
45 new ModuleFederationPlugin({
46 name: "shell",
47 remotes: {
48 dashboard: "dashboard@http://localhost:3001/remoteEntry.js",
49 analytics: "analytics@http://localhost:3002/remoteEntry.js",
50 },
51 shared: {
52 react: { singleton: true, requiredVersion: "^18.0.0" },
53 "react-dom": { singleton: true, requiredVersion: "^18.0.0" },
54 },
55 }),
56 ],
57};
Shared Dependencies & Versioning

The shared configuration controls how dependencies are deduplicated across federated boundaries. Webpack uses semantic versioning to determine which version of a shared module to use when multiple applications request different versions.

webpack.config.js (shared)
JavaScript
1// Shared dependency version resolution
2const deps = require("./package.json").dependencies;
3
4new ModuleFederationPlugin({
5 shared: {
6 // Exact version matching — strict
7 "react": {
8 singleton: true,
9 requiredVersion: deps.react,
10 // If the host provides React 18.2.0 and the remote needs ^18.0.0,
11 // the host's version is used. If the remote needs ^19.0.0, it will
12 // load its own React 19 separately (unless strictVersion is false).
13 },
14
15 // Loose version matching — share the closest compatible version
16 "lodash-es": {
17 singleton: false,
18 // Version 4.17.21 can satisfy ^4.0.0
19 requiredVersion: "^4.0.0",
20 },
21
22 // Shared with fallback — provide own version as fallback
23 "dayjs": {
24 singleton: true,
25 requiredVersion: deps.dayjs, // "1.11.7"
26 // If no remote provides dayjs, use this version
27 // It will be bundled into this application's initial chunk
28 },
29
30 // Package that should never be shared — each app loads own version
31 "moment-timezone": {
32 singleton: false,
33 requiredVersion: false, // Disable version checking
34 },
35 },
36});

info

Use singleton: true sparingly. Only libraries that must be singletons (React, Vue, Angular) need it. Utility libraries like lodash or date-fns benefit from not being singleton because newer versions can be loaded independently by different remotes without conflicts.
Micro-Frontend Architecture Patterns

Module Federation supports several architectural patterns for composing micro-frontends. Each pattern has tradeoffs in terms of coupling, deployment independence, and user experience.

PatternDescriptionBest For
Host + RemotesSingle shell app loads feature modules from external remotesEnterprise portals, admin panels
Dynamic RemotesRemotes loaded dynamically based on runtime configurationMulti-tenant apps, white-label solutions
Self-HealingEach remote can operate as a standalone app if host is unavailableMission-critical features, offline-capable apps
Nested FederationA remote itself hosts other federated modulesLarge organizations with nested teams
Islands ArchitectureIndependent federated modules with minimal host orchestrationContent-heavy sites, marketing pages

warning

Avoid deep nesting of federated modules (A → B → C). Each federation layer adds latency and complexity. Three or more levels of nesting often results in hard-to-debug dependency resolution issues and increased page load times.
Runtime Integration

Consuming federated modules at runtime is straightforward. The remote application loads its remoteEntry.js script, which registers the exposed modules with the Webpack runtime. The host then uses dynamic imports to load specific modules from the remote.

Runtime Integration.tsx
JavaScript
1// Host application — consuming remote modules at runtime
2import React, { Suspense, lazy } from "react";
3import ErrorBoundary from "./ErrorBoundary";
4
5// Lazy load the remote DashboardWidget component
6const DashboardWidget = lazy(() =>
7 import("dashboard/DashboardWidget")
8);
9
10// Lazy load remote Analytics components
11const MetricsGrid = lazy(() =>
12 import("analytics/MetricsGrid")
13);
14
15// Render with fallback and error boundaries
16function DashboardPage() {
17 return (
18 <div>
19 <h1>Dashboard</h1>
20 <ErrorBoundary fallback={<div>Widget failed to load</div>}>
21 <Suspense fallback={<div className="loading-pulse">Loading...</div>}>
22 <DashboardWidget userId={user.id} />
23 </Suspense>
24 </ErrorBoundary>
25
26 <ErrorBoundary fallback={<div>Metrics unavailable</div>}>
27 <Suspense fallback={<div className="loading-pulse">Loading metrics...</div>}>
28 <MetricsGrid dateRange={selectedRange} />
29 </Suspense>
30 </ErrorBoundary>
31 </div>
32 );
33}
34
35// Dynamic remote loading — remotes decided at runtime
36async function loadRemoteModule(remoteName, modulePath) {
37 // Dynamically load remoteEntry.js from a URL determined at runtime
38 const container = await import(/* webpackIgnore: true */ remoteName);
39 const factory = await container.get(modulePath);
40 return factory();
41}
42
43// Usage: load any remote module by URL
44const RemoteComponent = React.lazy(() =>
45 loadRemoteModule(
46 "https://feature-flags.example.com/remoteEntry.js",
47 "./FeatureFlag"
48 )
49);
Module Federation with React

React is the most common framework used with Module Federation. The key considerations are shared React instance, routing coordination, and state management across federated boundaries.

react-module-federation.tsx
JavaScript
1// Shared routing with React Router
2// Host application — provide router to federated modules
3import { BrowserRouter, Routes, Route } from "react-router-dom";
4import React, { Suspense, lazy } from "react";
5
6const DashboardWidget = lazy(() => import("dashboard/DashboardWidget"));
7const SettingsWidget = lazy(() => import("settings/SettingsWidget"));
8
9function AppShell() {
10 return (
11 <BrowserRouter>
12 <AppShell>
13 <Routes>
14 <Route path="/" element={<Home />} />
15 <Route
16 path="/dashboard/*"
17 element={
18 <Suspense fallback={<Loading />}>
19 <DashboardWidget />
20 </Suspense>
21 }
22 />
23 <Route
24 path="/settings/*"
25 element={
26 <Suspense fallback={<Loading />}>
27 <SettingsWidget />
28 </Suspense>
29 }
30 />
31 </Routes>
32 </AppShell>
33 </BrowserRouter>
34 );
35}
36
37// Remote application — handle routing within its scope
38// The remote receives the host's router context
39function DashboardWidget() {
40 // Use Routes inside the remote — it inherits the host's router
41 return (
42 <Routes>
43 <Route index element={<DashboardHome />} />
44 <Route path="analytics" element={<Analytics />} />
45 <Route path="reports" element={<Reports />} />
46 </Routes>
47 );
48}

info

For cross-application state sharing, avoid Redux or Zustand stores that are not federated-aware. Instead, use URL state (query params, hash) for simple data and custom event buses (window.dispatchEvent) for complex cross-app communication.
Module Federation with Angular

Angular works with Module Federation through the @angular-architects/module-federation package, which provides Angular-specific tooling and schematics for generating the required configuration.

webpack.config.js (Angular)
JavaScript
1// Angular Module Federation configuration
2// angular.json — custom webpack builder
3{
4 "projects": {
5 "shell": {
6 "architect": {
7 "build": {
8 "builder": "@angular-architects/module-federation:webpack-browser"
9 }
10 }
11 }
12 }
13}
14
15// webpack.config.js — Angular federation
16const { ModuleFederationPlugin } = require("webpack").container;
17const { shareAll } = require("@angular-architects/module-federation");
18
19module.exports = {
20 output: {
21 uniqueName: "mfe1",
22 publicPath: "auto",
23 },
24 optimization: {
25 runtimeChunk: false, // Required for Module Federation
26 },
27 plugins: [
28 new ModuleFederationPlugin({
29 name: "mfe1",
30 filename: "remoteEntry.js",
31 exposes: {
32 "./Module": "./src/app/flights/flights.module.ts",
33 },
34 shared: shareAll({
35 singleton: true,
36 strictVersion: true,
37 requiredVersion: "auto",
38 }),
39 }),
40 ],
41};
Alternatives to Module Federation

Module Federation is powerful but not the only approach to micro-frontends. Each alternative has different tradeoffs in terms of simplicity, performance, and team independence.

ApproachIntegrationPerformanceComplexity
Module FederationRuntime (Webpack)ExcellentHigh
iframeRuntime (native)PoorLow
Web ComponentsRuntime (native)GoodMedium
Single SPARuntime (orchestrator)GoodHigh
Nx / MonorepoBuild-timeExcellentMedium
Server-Side CompositionRequest-timeGoodMedium

info

Module Federation is best for organizations with 3-10 teams building a cohesive product. If you have fewer than 3 teams, a well-structured monorepo may be simpler and more performant. If you have more than 10 teams, consider combining Module Federation with Web Components for true decoupling.
Best Practices
  • Always use singletons for frameworks — React, Vue, Angular must be singletons with pinned versions across all federated applications.
  • Set a shared dependency strategy early — Define which libraries are shared and which are isolated before development starts. Changing shared configuration later can cause subtle bugs.
  • Use ErrorBoundaries — Every federated module import should be wrapped in an ErrorBoundary. A remote going down should never crash the host.
  • Prefer publicPath: "auto" — This ensures script paths are resolved relative to the current URL, making deployments location-independent.
  • Test remote failures — Write integration tests that simulate remote application failures to verify your error handling and fallback UI work correctly.
  • Monitor remoteEntry.js loads — Track the load time and success rate of remote entry scripts. A slow or failed remote load directly impacts user experience.
federation-monitoring.js
JavaScript
1// Module Federation health monitoring
2// Instrument remote entry loading with performance tracking
3async function loadFederatedModule(remoteName, modulePath) {
4 const startTime = performance.now();
5
6 try {
7 const container = await loadRemoteEntry(remoteName);
8 const factory = await container.get(modulePath);
9 const Module = factory();
10
11 const loadTime = performance.now() - startTime;
12 reportMetric("module-federation-load", {
13 remote: remoteName,
14 module: modulePath,
15 loadTime,
16 success: true,
17 });
18
19 return Module;
20 } catch (error) {
21 const loadTime = performance.now() - startTime;
22 reportMetric("module-federation-load", {
23 remote: remoteName,
24 module: modulePath,
25 loadTime,
26 success: false,
27 error: error.message,
28 });
29
30 // Fallback to a cached or default module
31 return getFallbackModule(remoteName, modulePath);
32 }
33}
$Blueprint — Engineering Documentation·Section ID: BT-MF·Revision: 1.0