Introduction
Module Federation enables micro-frontend architectures by allowing applications to dynamically share code at runtime. This guide covers everything you need to build scalable micro-frontend systems.
What is Module Federation?
Module Federation allows JavaScript applications to:
- Share code at runtime
- Load remote components
- Build independent deployments
- Scale horizontally
graph TB
subgraph "Host App"
Host[Main Application]
end
subgraph "Remote Apps"
Remote1[Checkout App]
Remote2[Product App]
Remote3[User App]
end
Host -->|load| Remote1
Host -->|load| Remote2
Host -->|load| Remote3
Configuration
Host Application
// webpack.config.js (host)
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
module.exports = {
mode: 'development',
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
checkout: 'checkout@http://localhost:3001/remoteEntry.js',
products: 'products@http://localhost:3002/remoteEntry.js',
user: 'user@http://localhost:3003/remoteEntry.js',
},
shared: ['react', 'react-dom'],
}),
],
};
Remote Application
// webpack.config.js (remote - checkout app)
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'checkout',
filename: 'remoteEntry.js',
exposes: {
'./CheckoutPage': './src/CheckoutPage.jsx',
'./CheckoutButton': './src/components/Button.jsx',
},
shared: ['react', 'react-dom'],
}),
],
};
Consuming Remotes
Dynamic Imports
// Host app - dynamically load remote
import { lazy, Suspense } from 'react';
const CheckoutPage = lazy(() => import('checkout/CheckoutPage'));
function App() {
return (
<Suspense fallback="Loading...">
<CheckoutPage />
</Suspense>
);
}
Fallback Strategies
// Provide fallback when remote unavailable
const CheckoutPage = lazy(() =>
import('checkout/CheckoutPage')
.catch(() => import('./LocalCheckoutFallback'))
);
Shared Dependencies
Version Management
// webpack.config.js
new ModuleFederationPlugin({
name: 'checkout',
exposes: {
'./Cart': './src/Cart.jsx',
},
shared: {
react: {
singleton: true,
requiredVersion: '^18.0.0',
eager: false,
},
'react-dom': {
singleton: true,
requiredVersion: '^18.0.0',
},
lodash: {
eager: true, // Load immediately
},
},
});
Singleton Pattern
// Use singleton for hooks
new ModuleFederationPlugin({
shared: {
'@scope/shared-lib': {
singleton: true,
eager: true,
requiredVersion: '^1.0.0',
},
},
});
State Management
Shared State
// Store in host
import { createContext } from 'react';
export const SharedContext = createContext(null);
// Provide in host app
function App() {
return (
<SharedContext.Provider value={globalState}>
<Router>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/checkout" element={<CheckoutRemote />} />
</Routes>
</Router>
</SharedContext.Provider>
);
}
CSS Isolation
Scoped Styles
// Use CSS Modules in remotes
// checkout/src/Checkout.module.css
.checkoutContainer {
padding: 20px;
}
// checkout/src/Checkout.jsx
import styles from './Checkout.module.css';
export function Checkout() {
return <div className={styles.checkoutContainer}>...</div>;
}
Shadow DOM
// For complete isolation, use Shadow DOM
const RemoteWrapper = ({ children }) => {
const shadowRoot = useRef(null);
if (!shadowRoot.current) {
shadowRoot.current = shadowRoot.attachShadow({ mode: 'open' });
}
return (
<div ref={shadowRoot}>
{children}
</div>
);
};
Build & Deployment
Independent Builds
// Each app has own build
// checkout/webpack.config.js
// products/webpack.config.js
// user/webpack.config.js
// Deploy independently
// checkout: deploy to s3://checkout.example.com
// products: deploy to s3://products.example.com
CI/CD Pipeline
# .github/workflows/deploy.yml
jobs:
deploy-checkout:
runs-on: checkout
steps:
- uses: actions/checkout@v4
- run: npm install
- run: npm run build
- run: npm run deploy
deploy-products:
runs-on: products
# Independent deployment
Use Cases
E-commerce
// Host: main-storefront.com
// Remote: checkout, products, user, search
Dashboard
// Host: dashboard.com
// Remote: analytics, reports, settings, notifications
Best Practices
1. Version Alignment
// Keep shared dependencies in sync across all apps
// Use package.json resolutions or workspaces
2. Error Boundaries
class ErrorBoundary extends React.Component {
componentDidCatch(error) {
// Log and show fallback
}
render() {
return this.props.children;
}
}
3. Type Sharing
// Share TypeScript types between apps
// Use npm workspace or git submodule for shared types
Conclusion
Module Federation enables:
- Independent deployments
- Code sharing at runtime
- Scalable architectures
- Team autonomy
Perfect for: Large applications, multiple teams, e-commerce, dashboards.
Comments