Module Federation
Module Federation enables runtime code sharing between micro frontends. Unlike build-time imports that bundle everything into a single artifact, Module Federation allows a host application to dynamically load code from remote applications at runtime—without redeploying the host when remotes change.
This guide explains what Module Federation is, how it works, and how to use it effectively in micro frontend architectures.
What Module Federation Is and Why It Matters
Section titled “What Module Federation Is and Why It Matters”Module Federation was introduced in Webpack 5. It allows JavaScript applications to share code at runtime by exposing “remote” modules that a “host” application can consume as if they were local dependencies.
Why it matters for micro frontends:
- Independent deployments — Team A deploys their micro frontend; the host picks it up automatically on next load. No host redeployment required.
- Shared dependencies without duplication — React, shared UI components, and utilities can be loaded once and shared.
- Runtime flexibility — Different environments or A/B tests can load different remote versions.
Module Federation fits into client-side composition: the host loads remote bundles dynamically and mounts them. For managing shared dependencies (React, state libraries), see shared dependencies.
The Problem It Solves
Section titled “The Problem It Solves”Without Module Federation (or similar techniques):
- Duplicate bundles: Each micro frontend bundles its own copy of React, lodash, etc., inflating total page size.
- Tight coupling: Sharing code requires npm packages and coordinated releases.
- Deployment coupling: Adding a new micro frontend or updating one requires rebuilding and redeploying the shell.
Module Federation lets you share code at runtime, negotiate versions automatically, and load remote applications without host redeployment.
Core Concepts
Section titled “Core Concepts”Host and Remote Applications
Section titled “Host and Remote Applications”- Host: The shell or container that loads and orchestrates micro frontends. It consumes remotes.
- Remote: A micro frontend that exposes modules for the host (or other remotes) to consume.
A single application can be both host and remote: it loads remotes and also exposes itself for others to load.
Exposed Modules
Section titled “Exposed Modules”A remote exposes specific modules via a public API. The host imports them by name:
// Host imports from remoteconst ProductCatalog = React.lazy(() => import('product_catalog/ProductCatalog'));The remote’s build produces a remoteEntry.js (or similar) that defines these exposed modules.
Shared Dependencies
Section titled “Shared Dependencies”Module Federation can share dependencies between host and remotes. Instead of each app bundling React, the shared configuration ensures only one copy loads:
shared: { react: { singleton: true, requiredVersion: '^18.0.0' }, 'react-dom': { singleton: true, requiredVersion: '^18.0.0' },}Singleton Sharing
Section titled “Singleton Sharing”singleton: true guarantees only one instance of the shared module exists across host and all remotes. Critical for React, Redux, and other libraries that assume a single instance.
Webpack Module Federation
Section titled “Webpack Module Federation”Configuration Example
Section titled “Configuration Example”Remote (product-catalog) webpack config:
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = { plugins: [ new ModuleFederationPlugin({ name: 'product_catalog', filename: 'remoteEntry.js', exposes: { './ProductCatalog': './src/ProductCatalog', './ProductDetail': './src/ProductDetail', }, shared: { react: { singleton: true, requiredVersion: '^18.0.0' }, 'react-dom': { singleton: true, requiredVersion: '^18.0.0' }, }, }), ],};Host webpack config:
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = { plugins: [ new ModuleFederationPlugin({ name: 'host', remotes: { product_catalog: 'product_catalog@https://cdn.example.com/product-catalog/remoteEntry.js', checkout: 'checkout@https://cdn.example.com/checkout/remoteEntry.js', }, shared: { react: { singleton: true, requiredVersion: '^18.0.0' }, 'react-dom': { singleton: true, requiredVersion: '^18.0.0' }, }, }), ],};How It Works Under the Hood
Section titled “How It Works Under the Hood”- remoteEntry.js — The remote builds a small entry file that registers its exposed modules in a global scope.
- Async loading — When the host executes
import('product_catalog/ProductCatalog'), Webpack generates a dynamic import that fetches the remote’s remoteEntry (if not cached), then loads the requested chunk. - Chunk splitting — Exposed modules and shared dependencies are split into separate chunks. Shared chunks load once and are reused.
Vite and Rspack Module Federation Support
Section titled “Vite and Rspack Module Federation Support”Vite does not include Module Federation natively. The community plugin @originjs/vite-plugin-federation provides similar functionality:
import federation from '@originjs/vite-plugin-federation';
export default { plugins: [ federation({ name: 'product_catalog', filename: 'remoteEntry.js', exposes: { './ProductCatalog': './src/ProductCatalog.tsx', }, shared: ['react', 'react-dom'], }), ],};Rspack
Section titled “Rspack”Rspack is a Rust-based bundler compatible with Webpack’s Module Federation plugin. Configuration is analogous to Webpack 5.
Runtime vs Build-Time Sharing Trade-offs
Section titled “Runtime vs Build-Time Sharing Trade-offs”| Aspect | Runtime (Module Federation) | Build-Time (npm packages) |
|---|---|---|
| Deployment | Independent per app | Coordinated releases |
| Version negotiation | At runtime | At build time |
| Bundle size | Shared code loaded once | May duplicate if versions differ |
| Complexity | Higher (async loading, version resolution) | Lower |
| Flexibility | High (A/B test remotes) | Lower |
Use Module Federation when deployment independence and runtime flexibility matter. Use build-time sharing when simplicity and tighter control are preferred.
Common Patterns
Section titled “Common Patterns”Shared UI Libraries
Section titled “Shared UI Libraries”Expose a design system or component library as a remote:
// design-system remoteexposes: { './Button': './src/Button', './Input': './src/Input', './Theme': './src/Theme',}Micro frontends import from the design system remote instead of bundling their own components.
Shared Utility Functions
Section titled “Shared Utility Functions”Expose utilities for consistency across micro frontends:
exposes: { './utils': './src/utils', './api': './src/api',}Federated Design System Components
Section titled “Federated Design System Components”A design system team maintains a remote; product teams consume it. Updates deploy independently—no need to bump package versions in every consumer.
Pitfalls and Gotchas
Section titled “Pitfalls and Gotchas”Version Mismatches
Section titled “Version Mismatches”If host expects React 18 and a remote bundles React 17, you may get hooks errors or duplicate React instances. Use shared with requiredVersion and strictVersion:
shared: { react: { singleton: true, requiredVersion: '^18.0.0', strictVersion: true, // Fail if version incompatible },}Circular Dependencies
Section titled “Circular Dependencies”Host loads Remote A, which loads Remote B, which loads the Host. Avoid circular remotes; use a shared kernel or event bus for cross-app communication instead.
TypeScript Type Sharing
Section titled “TypeScript Type Sharing”Module Federation shares runtime code, not TypeScript types. Options:
- Publish type definitions as a separate package
- Use a monorepo with shared tsconfig paths
- Define types in a
@typespackage consumed by host and remotes
Code Example: Host and Remote Config
Section titled “Code Example: Host and Remote Config”Remote (product-catalog):
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = { output: { publicPath: 'https://cdn.example.com/product-catalog/', }, plugins: [ new ModuleFederationPlugin({ name: 'product_catalog', filename: 'remoteEntry.js', exposes: { './App': './src/App', }, shared: { react: { singleton: true, requiredVersion: '^18.2.0' }, 'react-dom': { singleton: true, requiredVersion: '^18.2.0' }, }, }), ],};Host:
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = { plugins: [ new ModuleFederationPlugin({ name: 'host', remotes: { product_catalog: 'product_catalog@https://cdn.example.com/product-catalog/remoteEntry.js', }, shared: { react: { singleton: true, requiredVersion: '^18.2.0' }, 'react-dom': { singleton: true, requiredVersion: '^18.2.0' }, }, }), ],};// Host App.jsxconst ProductCatalogApp = React.lazy(() => import('product_catalog/App'));
function App() { return ( <Suspense fallback={<Loading />}> <ProductCatalogApp /> </Suspense> );}Summary
Section titled “Summary”Module Federation enables runtime code sharing between independently deployed micro frontends. Use it when deployment independence and shared dependencies matter. Configure host and remotes carefully, manage version alignment for singletons, and avoid circular dependencies.
For dependency strategies beyond Module Federation, see shared dependencies. For where Module Federation fits in the composition landscape, see composition strategies.
Go Deeper in the Book
This topic is covered in depth in Chapters 10-11 of Micro Frontends Architecture for Scalable Applications, with detailed examples, diagrams, and production-ready patterns.