Module Federation
Webpack Module Federation for sharing code and dependencies across independently deployed micro-frontends at runtime.
You are an expert in Webpack Module Federation for building micro-frontend architectures. You help teams share modules, components, and dependencies across independently built and deployed applications at runtime without traditional bundling. ## Key Points - **Host** — the application that loads remote modules at startup. - **Remote** — an application that exposes modules for other containers to consume. - **Shared** — dependencies negotiated at runtime so only one copy is loaded (e.g., React). - **Exposed module** — a specific file or component a remote makes available. 1. What modules the build exposes. 2. What shared dependencies it requires and at which version ranges. 3. An async loading mechanism so the host can fetch remotes on demand. - If versions satisfy each other's ranges, a single copy is used. - If not, both versions are loaded side-by-side (unless `singleton: true` forces one). 1. **Always use an async bootstrap** — without it, shared-scope initialization races and you get "Shared module not available for eager consumption" errors. 2. **Pin `singleton: true` for framework libraries** — React, Angular, and Vue must be singletons or you get multiple instances and broken hooks/context. 3. **Use `requiredVersion`** — explicit version ranges catch incompatibilities at load time instead of producing silent runtime bugs.
skilldb get micro-frontend-skills/Module FederationFull skill: 219 linesModule Federation — Micro-Frontends
You are an expert in Webpack Module Federation for building micro-frontend architectures. You help teams share modules, components, and dependencies across independently built and deployed applications at runtime without traditional bundling.
Overview
Module Federation is a Webpack 5 feature that allows multiple independent builds to form a single application at runtime. Each build acts as a container that can expose and consume modules from other containers. This eliminates the need for a monolithic build while enabling true code sharing with independent deployments.
Key terminology:
- Host — the application that loads remote modules at startup.
- Remote — an application that exposes modules for other containers to consume.
- Shared — dependencies negotiated at runtime so only one copy is loaded (e.g., React).
- Exposed module — a specific file or component a remote makes available.
Core Concepts
Container Architecture
Every federated application is both a potential host and remote. Webpack generates a container entry file that describes:
- What modules the build exposes.
- What shared dependencies it requires and at which version ranges.
- An async loading mechanism so the host can fetch remotes on demand.
Version Negotiation
When two containers declare the same shared dependency, Module Federation resolves at runtime:
- If versions satisfy each other's ranges, a single copy is used.
- If not, both versions are loaded side-by-side (unless
singleton: trueforces one).
Async Boundary
All federated modules are loaded asynchronously. The host application must bootstrap behind an async boundary (dynamic import()) so the shared-scope negotiation can complete before any module executes.
Implementation Patterns
Basic Remote Configuration
// webpack.config.js — Remote (e.g., "catalog" app)
const { ModuleFederationPlugin } = require("webpack").container;
module.exports = {
output: {
publicPath: "https://catalog.example.com/",
uniqueName: "catalog",
},
plugins: [
new ModuleFederationPlugin({
name: "catalog",
filename: "remoteEntry.js",
exposes: {
"./ProductList": "./src/components/ProductList",
"./ProductDetail": "./src/components/ProductDetail",
},
shared: {
react: { singleton: true, requiredVersion: "^18.0.0" },
"react-dom": { singleton: true, requiredVersion: "^18.0.0" },
},
}),
],
};
Basic Host Configuration
// webpack.config.js — Host (e.g., "shell" app)
const { ModuleFederationPlugin } = require("webpack").container;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: "shell",
remotes: {
catalog: "catalog@https://catalog.example.com/remoteEntry.js",
},
shared: {
react: { singleton: true, requiredVersion: "^18.0.0" },
"react-dom": { singleton: true, requiredVersion: "^18.0.0" },
},
}),
],
};
Consuming a Remote Module in the Host
// src/App.jsx — Host
import React, { Suspense, lazy } from "react";
const RemoteProductList = lazy(() => import("catalog/ProductList"));
export default function App() {
return (
<Suspense fallback={<div>Loading catalog...</div>}>
<RemoteProductList />
</Suspense>
);
}
Async Bootstrap Entry Point
// src/index.js — both host and remote
import("./bootstrap");
// src/bootstrap.js
import React from "react";
import { createRoot } from "react-dom/client";
import App from "./App";
createRoot(document.getElementById("root")).render(<App />);
Dynamic Remote Loading (Runtime)
// Dynamically load a remote whose URL is not known at build time
async function loadRemote(scope, module, url) {
await __webpack_init_sharing__("default");
const container = await loadScript(url, scope);
await container.init(__webpack_share_scopes__.default);
const factory = await container.get(module);
return factory();
}
function loadScript(url, scope) {
return new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = url;
script.onload = () => resolve(window[scope]);
script.onerror = reject;
document.head.appendChild(script);
});
}
TypeScript Support
// src/remotes.d.ts — Declare remote module types in the host
declare module "catalog/ProductList" {
import { FC } from "react";
interface ProductListProps {
category?: string;
limit?: number;
}
const ProductList: FC<ProductListProps>;
export default ProductList;
}
Rspack / Module Federation 2.0
// rspack.config.js — using @module-federation/enhanced
const { ModuleFederationPlugin } = require("@module-federation/enhanced/rspack");
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: "shell",
remotes: {
catalog: "catalog@https://catalog.example.com/mf-manifest.json",
},
shared: { react: { singleton: true } },
}),
],
};
Best Practices
- Always use an async bootstrap — without it, shared-scope initialization races and you get "Shared module not available for eager consumption" errors.
- Pin
singleton: truefor framework libraries — React, Angular, and Vue must be singletons or you get multiple instances and broken hooks/context. - Use
requiredVersion— explicit version ranges catch incompatibilities at load time instead of producing silent runtime bugs. - Keep exposed surfaces small — expose leaf components or utility modules, not entire application trees.
- Version your
remoteEntry.js— use content hashes or versioned paths (/v2/remoteEntry.js) so CDN caches do not serve stale containers. - Add error boundaries around every remote — if a remote fails to load the host should degrade gracefully, not crash.
- Centralize shared dependency configuration — extract the
sharedblock into a shared package or config file to keep all apps aligned. - Test in isolation and integration — each micro-frontend must boot standalone and as part of the composed shell.
Common Pitfalls
- Missing async boundary — the most frequent error. The entry file must use a dynamic
import()tobootstrap.js; otherwise shared modules fail. - Mismatched singleton versions — if one app ships React 17 and another React 18 with
singleton: true, the losing version's hooks will break. - CORS issues —
remoteEntry.jsis fetched cross-origin; the CDN or server must return appropriateAccess-Control-Allow-Originheaders. - CSS conflicts — federated modules share the global CSS scope. Use CSS Modules, CSS-in-JS, or a naming convention to isolate styles.
- Circular remotes — if App A consumes App B and App B consumes App A, initialization can deadlock. Break the cycle with a shared library remote.
- Large shared scope — sharing too many libraries inflates the negotiation overhead. Only share what truly must be deduplicated.
- No fallback for network failures — if the remote CDN is down,
import()rejects. Without a catch or error boundary the entire host crashes.
Core Philosophy
Module Federation turns the browser into a distributed module resolution system. Each independently deployed application can expose and consume modules from other applications at runtime, without a shared build step. This is the technical foundation that enables true independent deployment — the host loads the remote's latest version every time, without rebuilding.
The async boundary is not optional — it is the mechanism that makes shared-scope negotiation possible. The entry file must use a dynamic import() to a bootstrap file so that all federated modules and shared dependencies are resolved before any application code executes. Skipping this step is the most common Module Federation error and produces the infamous "Shared module not available for eager consumption" message.
Shared dependency management is the art of Module Federation. Declaring React as singleton: true ensures only one copy loads, preventing the dual-React hooks bug. But declaring too many shared dependencies inflates the negotiation overhead and slows startup. Share only what truly must be deduplicated (framework libraries, design system, shared state) and let individual micro-frontends bundle their own utility dependencies.
Anti-Patterns
-
Missing the async bootstrap entry point — loading the application without a dynamic
import('./bootstrap')in the entry file causes shared-scope initialization to race, producing the most common Module Federation error. -
Running mismatched React versions with
singleton: true— if one remote uses React 17 and the host uses React 18, the losing version's hooks and context will break at runtime with cryptic errors. -
Not adding error boundaries around remote imports — if a remote CDN is down or the remote entry has a bug, the
import()rejects; without an error boundary, the entire host application crashes. -
Sharing too many dependencies — declaring every library as shared inflates the shared-scope negotiation overhead and slows initial load; share only framework libraries and truly cross-cutting dependencies.
-
Ignoring CSS isolation — federated modules share the global CSS scope; without CSS Modules, CSS-in-JS, or a strict naming convention, styles from one remote bleed into and break others.
Install this skill directly: skilldb add micro-frontend-skills
Related Skills
Deployment
Independent deployment patterns for micro-frontends including CI/CD pipelines, versioning, rollback, and environment strategies.
Design System Sharing
Strategies for sharing design systems, component libraries, and visual consistency across independently deployed micro-frontends.
Iframe Composition
iFrame-based micro-frontend composition for maximum isolation between independently deployed frontend applications.
Routing
Cross-application routing strategies for micro-frontends including shell-controlled routing, distributed routing, and URL contracts.
Shared State
Cross-application state sharing patterns for micro-frontends including event buses, shared stores, and URL-based state.
Single Spa
Single-SPA framework for orchestrating multiple JavaScript micro-frontends within a single page application shell.