Introduction
As applications scale, maintaining a monolithic front-end becomes challenging: long build times, interdependent releases, and difficulty adopting new technologies. Microfrontends solve these issues by breaking a UI into independently developed and deployed modules. Webpack Module Federation allows these modules—called remotes—to expose components that a central host or container app can load at runtime. This detailed guide walks through planning, configuring, developing, and deploying a fully federated microfrontend architecture.

1. Plan Your Microfrontend Architecture
- Identify Bounded Contexts
- Split your UI by business domain: e.g., catalog, checkout, user-profile.
- Each domain becomes a remote application with its own repo, CI/CD, and release cycle.
- Define Shared Libraries
- Common dependencies (e.g., React, ReactDOM, utility libraries) must be shared to avoid duplicates.
- Decide which packages are singletons and required versions.
- Routing Strategy
- The container owns top-level routing; remotes register their root component on specific routes.
- Use React Router (or your framework’s router) in the container to mount remotes.
2. Configure Module Federation
2.1 Remote Configuration
In the remote’s webpack.config.js
, expose components and declare shared deps:
jsCopyEdit// projects/shop/webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
entry: './src/bootstrap.js',
mode: 'production',
output: { publicPath: 'auto' },
plugins: [
new ModuleFederationPlugin({
name: 'shop',
filename: 'remoteEntry.js', // Exposed manifest
exposes: {
'./ProductCard': './src/ProductCard'
},
shared: {
react: { singleton: true, eager: true, requiredVersion: '^17.0.0' },
'react-dom': { singleton: true, eager: true, requiredVersion: '^17.0.0' }
}
})
]
};
name
: Global var under which the remote is registered.filename
: The runtime manifest loaded by the host.exposes
: Key/value pairs of exposed module paths.shared
: Ensures a single instance of React across remotes and container.
2.2 Host Configuration
In the container’s webpack.config.js
, declare remotes and shared deps:

jsCopyEdit// projects/container/webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;
module.exports = {
entry: './src/index.js',
mode: 'production',
output: { publicPath: 'auto' },
plugins: [
new ModuleFederationPlugin({
remotes: {
shop: 'shop@https://cdn.example.com/shop/remoteEntry.js'
},
shared: {
react: { singleton: true, eager: true, requiredVersion: '^17.0.0' },
'react-dom': { singleton: true, eager: true, requiredVersion: '^17.0.0' }
}
})
]
};
remotes
: Maps the remote name (shop
) to the URL of itsremoteEntry.js
.
3. Bootstrap Remote Applications
3.1 Remote Entry Point
Ensure remotes initialize themselves before exposing modules. In src/bootstrap.js
:
jsCopyEditimport('./bootstrapApp').catch(console.error);
// src/bootstrapApp.js
import React from 'react';
import ReactDOM from 'react-dom';
import ProductCard from './ProductCard';
ReactDOM.render(
<ProductCard productId={123} />,
document.getElementById('root')
);
- Dynamic import of
bootstrapApp
prevents code loading before Module Federation sets up.
4. Consume Remotes in the Container
4.1 Dynamic Imports with React.lazy
In the container’s routing or component:
jsxCopyEdit// src/App.jsx
import React, { Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const ShopApp = React.lazy(() => import('shop/ProductCard'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/shop" element={<ShopApp />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
export default App;
import('shop/ProductCard')
resolves to the exposed module in the remote.
5. Local Development and CORS
5.1 DevServer Settings
In each app’s webpack.config.js
, configure the dev server for cross-origin requests:
jsCopyEditdevServer: {
port: 3001,
headers: { 'Access-Control-Allow-Origin': '*' },
hot: true
},
output: { publicPath: 'auto' },
- Run container on
3000
, shop remote on3001
. publicPath: 'auto'
lets Webpack determine URL at runtime.
6. Deployment and Versioning

- Build and Publish Remotes bashCopyEdit
cd projects/shop npm run build # Deploy dist/ to CDN at /shop/<version>/
- Update Remote URL in Container
Automate URL updates in CI/CD using a script: bashCopyEditVERSION=$(git describe --tags) sed -i "s|shop@.*|shop@https://cdn.example.com/shop/${VERSION}/remoteEntry.js|" projects/container/webpack.config.js
- Canary Releases
- Deploy new remote versions to a staging CDN path.
- Point container’s remotes config to staging for smoke tests before production.
7. Best Practices
- Error Boundaries: Wrap remotes to catch load or runtime errors. jsxCopyEdit
<ErrorBoundary fallback={<div>Failed to load</div>}> <Suspense fallback={<div>Loading...</div>}> <ShopApp /> </Suspense> </ErrorBoundary>
- Auth Handling: Keep authentication logic in the container and pass tokens as props or via a shared context.
- Performance: Preload critical remotes with
<link rel="preload">
in the container’s HTML. - Monitoring: Use Real User Monitoring (RUM) to track chunk load times and failures.
Conclusion

Webpack Module Federation empowers teams to build scalable, independently deployable microfrontends, improving development velocity and performance. By defining clear boundaries, sharing dependencies as singletons, and automating deployments, you can evolve monolithic UIs into federated architectures. Start with a proof-of-concept remote, refine configurations, and gradually onboard additional domains—transforming your front-end into a resilient, team-friendly ecosystem.