Introduction
As frontend applications grow in scale and teams multiply, monolithic builds become unwieldy: long compile times, large bundles, and tight coupling between independently deployable features. Webpack Module Federation addresses these pain points by enabling runtime code sharing across separately built and deployed applications. Rather than bundling every shared component or utility into each consumer’s build, hosts load modules dynamically from remotes on demand—driving:
- Independent Deployability: Teams deploy micro‑frontends without coordinating full‑app releases.
- Bundle Size Reduction: Shared code lives in one place; hosts only fetch what they need.
- Version Safety: Shared dependencies (e.g., React) load once as singletons, preventing mismatches.
- Runtime Flexibility: Hosts can switch remote versions via configuration, feature‑flag remotes, or A/B test entire micro‑frontends.

In this in‑depth guide, we’ll cover:
- Module Federation Basics: Key concepts and terminology
- Hands‑On Example: Building a
feature-app(remote) andapp-shell(host) from scratch - Advanced Configuration: Shared dependency strategies, eager vs. lazy loading, fallback remotes
- Dynamic Remotes: Installing remote URLs at runtime and environment‑driven deployments
- Performance and Caching: Lazy loading, prefetching, and caching considerations
- Error Handling & Resilience: Health checks, timeouts, and graceful degradation
- Security Implications: CORS, code integrity, and sandboxing
- CI/CD & Testing Strategies: Verifying federation boundaries in pipelines
- Best Practices & Anti‑Patterns: Organizing exposes, versioning, and maintaining a healthy federation
By the end, you’ll have a blueprint for powering your micro‑frontend architecture with Module Federation—maximizing reuse, minimizing duplication, and scaling team autonomy.
1. Module Federation Basics
1.1 Core Terminology
| Term | Description |
|---|---|
| Host | The “shell” app that consumes federated modules at runtime. |
| Remote | An independently built app that exposes modules via a remoteEntry.js manifest. |
| Exposes | A mapping in a remote’s Webpack config: local source paths → federated module names (e.g., ./Widget). |
| Remotes | A mapping in a host’s Webpack config: federated names → <global>@<URL>/remoteEntry.js. |
| Shared | Dependencies declared in both host & remote to ensure a singleton instance (e.g., React, lodash). |
1.2 Runtime Flow
- Bootstrap: The host’s main bundle loads in the browser.
- Discovery: When code calls
import('remoteApp/Module'), Webpack fetchesremoteEntry.jsfrom the remote URL. - Resolution: Using the manifest, Webpack resolves and executes chunk files for that module.
- Sharing: If the module depends on shared libraries, Webpack loads or reuses a single instance—avoiding duplicates.
A simplified diagram:
pgsqlCopyEdit[ Host Bundle ] -> import('remoteA/Button')
\
fetch http://remoteA/remoteEntry.js
-> manifest with mapping
fetch chunks for Button
mount Button component in Host
2. Hands‑On Example: Remote and Host Setup
We’ll build two CRA-based React apps: feature-app (remote) on port 3001, and app-shell (host) on port 3000.
2.1 Initialize Projects
bashCopyEdit# Feature app (remote)
npx create-react-app feature-app
cd feature-app
npm install webpack@5 webpack-cli@4
# Shell app (host)
cd ..
npx create-react-app app-shell
cd app-shell
npm install webpack@5 webpack-cli@4
2.2 Remote Configuration (feature-app)
Create or extend webpack.config.js in feature-app/:
jsCopyEditconst { override, addWebpackPlugin } = require('customize-cra');
const ModuleFederationPlugin = require('webpack').container.ModuleFederationPlugin;
module.exports = override(
addWebpackPlugin(
new ModuleFederationPlugin({
name: 'featureApp',
filename: 'remoteEntry.js',
exposes: {
'./FeatureWidget': './src/FeatureWidget'
},
shared: {
react: { singleton: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, requiredVersion: '^18.0.0' }
}
})
)
);
FeatureWidget Component (src/FeatureWidget.js):
jsxCopyEditimport React from 'react';
export default function FeatureWidget() {
return (
<div style={{
border: '2px dashed #007acc',
padding: '1rem',
margin: '1rem 0'
}}>
<h2>Federated Feature Widget</h2>
<p>This was loaded dynamically from another app!</p>
</div>
);
}
Expose it by modifying your app’s start script for port:
jsoncCopyEdit// package.json
"scripts": {
"start": "PORT=3001 react-scripts start",
// ...
}
Run:
bashCopyEditcd feature-app
npm start
A remoteEntry.js file will now be served at http://localhost:3001/remoteEntry.js.
2.3 Host Configuration (app-shell)
In app-shell/ webpack.config.js:

jsCopyEditconst { override, addWebpackPlugin } = require('customize-cra');
const ModuleFederationPlugin = require('webpack').container.ModuleFederationPlugin;
module.exports = override(
addWebpackPlugin(
new ModuleFederationPlugin({
name: 'appShell',
remotes: {
featureApp: 'featureApp@http://localhost:3001/remoteEntry.js'
},
shared: {
react: { singleton: true, eager: false, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, eager: false, requiredVersion: '^18.0.0' }
}
})
)
);
Consume the remote component in src/App.js:
jsxCopyEditimport React, { Suspense, lazy } from 'react';
const FeatureWidget = lazy(() => import('featureApp/FeatureWidget'));
function App() {
return (
<div>
<h1>Application Shell</h1>
<p>This is the host app.</p>
<Suspense fallback={<div>Loading feature…</div>}>
<FeatureWidget />
</Suspense>
</div>
);
}
export default App;
Start the shell:
bashCopyEditcd app-shell
npm start
Visit http://localhost:3000: you should see content from both the shell and your federated feature.
3. Advanced Configuration
3.1 Shared Dependency Strategies
| Option | Behavior |
|---|---|
singleton | Ensures only one version is loaded across host & remotes. |
eager | When true, loads the shared module upfront (in initial chunk) rather than lazily on demand. |
requiredVersion | Throws a warning or error if versions don’t overlap semantically. |
Example: Eager Singleton React
jsCopyEditshared: {
react: { singleton: true, eager: true, requiredVersion: '^18.0.0' },
'react-dom': { singleton: true, eager: true, requiredVersion: '^18.0.0' }
}
Use eager: true when you need shared modules to be available synchronously (e.g., for hooks or context).
3.2 Handling Version Mismatches
If a remote was built with React ^17.0.0 but your host uses ^18.0.0, you may:
- Update remote to match host.
- Relax
requiredVersionto allow^17 || ^18. - Force host version by
eager: true, so host provides React to remotes.
jsCopyEditreact: {
singleton: true,
eager: true,
requiredVersion: '^17.0.0 || ^18.0.0'
}
3.3 Multiple Remotes
Hosts can consume several remotes:
jsCopyEditremotes: {
featureApp: 'featureApp@https://cdn.example.com/featureApp/remoteEntry.js',
utilsLib: 'utilsLib@https://cdn.example.com/utilsLib/remoteEntry.js'
}
Import as needed:
jsCopyEditconst Util = lazy(() => import('utilsLib/UtilityFunction'));
4. Dynamic Remotes & Environment‑Driven URLs
Hard‑coding remote URLs ties your builds to one environment. Instead, use dynamic remotes:
jsCopyEditnew ModuleFederationPlugin({
name: 'appShell',
remotes: {
featureApp: `featureApp@[window.featureAppUrl]/remoteEntry.js`
},
// ...
});
Inject window.featureAppUrl in your HTML template or via a small runtime script:
htmlCopyEdit<!DOCTYPE html>
<html>
<head>
<!-- ... -->
<script>
window.featureAppUrl = process.env.FEATURE_APP_URL || 'https://cdn.example.com/featureApp';
</script>
</head>
<body>
<div id="root"></div>
<script src="main.js"></script>
</body>
</html>
Set FEATURE_APP_URL per environment (staging, prod) without rebuilding the host.
5. Performance & Caching Considerations
5.1 Lazy Loading & Prefetching

By default, remotes load on demand via import(). You can also prefetch to improve UX:
jsxCopyEditimport('featureApp/FeatureWidget' /* webpackPrefetch: true */);
This hints the browser to fetch chunks during idle time.
5.2 Cache‑Busting
RemoteEntry and chunks should be fingerprinted in production for cache invalidation:
- Configure your build to emit
remoteEntry.[contenthash].js. - Update host’s remotes mapping via a manifest or dynamic lookup.
5.3 Concurrent Requests
Avoid performance cliffs by exposing too many small modules. Instead:
- Bundle related features into one federated entry (e.g.,
./UIComponents). - Control chunk sizes with webpack’s
optimization.splitChunksin the remote.
6. Error Handling & Resilience
6.1 Health‑Check Fallback
Wrap imports to catch load failures:
jsxCopyEditasync function loadWidget() {
try {
const { default: Widget } = await import('featureApp/FeatureWidget');
return Widget;
} catch (e) {
console.error('Failed to load federated widget', e);
return null; // or return a fallback component
}
}
const FeatureWidget = React.lazy(loadWidget);
6.2 Timeouts
You can implement a timeout wrapper:
jsxCopyEditfunction withTimeout(promise, ms) {
let id;
const timeout = new Promise((_, reject) =>
id = setTimeout(() => reject(new Error('Load timeout')), ms)
);
return Promise.race([promise, timeout]).finally(() => clearTimeout(id));
}
const FeatureWidget = React.lazy(() =>
withTimeout(import('featureApp/FeatureWidget'), 5000)
);
If the remote is slow or unreachable, you degrade gracefully.
7. Security Implications
7.1 CORS Configuration
Ensure the remote’s server serves remoteEntry.js with:
makefileCopyEditAccess-Control-Allow-Origin: *
or restrict to specific hosts for tighter security.
7.2 Code Integrity
Consider using Subresource Integrity (SRI) for remoteEntry:
htmlCopyEdit<script
src="https://cdn/.../remoteEntry.js"
integrity="sha384-abc123..."
crossorigin="anonymous">
</script>
SRI prevents malicious tampering of remote scripts.
7.3 Sandboxing
Federated code runs in the host’s JS context. Avoid remote code having elevated privileges or access to host internals unless explicitly intended.
8. CI/CD & Testing Strategies
8.1 Automated Federation Tests
- Spin up host and remote locally in your CI job.
- Run a smoke test that renders the host page and verifies remote components appear.
- Use Puppeteer or Playwright to check for console errors, missing network requests, and correct rendering.

8.2 Version Contract Checks
- CI step to compare host’s expected
remoteEntryversion (e.g., from manifest) against the remote’s deployed version endpoint. - Fail the build if the remote API contract is out of sync.
8.3 Canary Releases
- Deploy a new remote version behind a feature flag or alternate remote name (e.g.,
featureAppBeta). - Allow a percentage of hosts to consume the beta remote by toggling which remote URL they load.
9. Best Practices & Anti‑Patterns
| Best Practice | Anti‑Pattern |
|---|---|
Group related modules under one expose (e.g., ./UI) | Exposing dozens of tiny modules individually |
| Use semantic versioning for remotes and update hosts via manifest | Hard‑coding remote version in host build |
| Document federated APIs and shared contracts | Implicit dependencies and undocumented expects |
| Health checks and fallbacks | Letting load failures crash the entire app |
| Automate CI federation tests | Manual checks prone to oversight |
Conclusion
Webpack Module Federation provides a powerful framework for runtime code sharing—unlocking true micro‑frontend and distributed team architectures. By configuring remotes, exposes, and shared dependencies, you achieve:
- Independent deployments: Remote apps roll out features on their own schedules.
- Smaller host bundles: Only load code you need, when you need it.
- Singletons: No duplicate React or utility libraries.
- Dynamic routing: Switch remote versions without host rebuilds.
- Resilience: Fallbacks and timeouts keep your app stable even when remotes fail.
Start by federating a small widget, then expand to shared libraries, utility modules, and entire feature sets. Couple federation with robust CI tests, semantic versioning, and health‑check fallbacks, and you’ll have a scalable, maintainable micro‑frontend ecosystem that grows with your teams and user needs























































































































































































































































































































































































































































































































































































































































































