Using Webpack 5 Module Federation to Share UI Code Across Microfrontends
Learn how Module Federation lets teams load remote components at runtime, avoid duplicate bundles, and keep shared dependencies in sync.
06 Sept 2026, 17:12 UTC

Problem: duplicated libraries and slow loads
When multiple teams build UI components independently, each team’s webpack build often bundles its own copy of React, lodash, or other shared libraries. The resulting host page loads several copies of the same code, wasting bandwidth and increasing the risk of version mismatches that cause runtime errors.
Thesis: Module Federation solves this by exposing selected modules as runtime‑loadable containers
Webpack 5’s Module Federation plugin lets a remote build declare which modules it wants to share. A host application can then consume those modules as if they were local, while webpack ensures that shared dependencies (like React) are loaded only once, provided their versions are compatible.
Worked example: sharing a Button component
We’ll set up two projects:
- Remote – exposes a Button component.
- Host – consumes the Button from the remote.
Remote webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;
const path = require('path');
module.exports = {
entry: './src/index.js',
mode: 'development',
devServer: {
port: 3001,
headers: { 'Access-Control-Allow-Origin': '*' },
},
output: {
publicPath: 'http://localhost:3001/',
clean: true,
},
plugins: [
new ModuleFederationPlugin({
name: 'ui',
filename: 'remoteEntry.js',
exposes: {
'./Button': './src/Button.jsx',
},
shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
}),
],
};
Place this file in the remote project’s root. The shared section tells webpack to treat React and React‑DOM as singletons, meaning only one copy will be loaded even if the host also depends on them.
Host webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container;
const path = require('path');
module.exports = {
entry: './src/index.js',
mode: 'development',
devServer: {
port: 3000,
headers: { 'Access-Control-Allow-Origin': '*' },
},
output: {
publicPath: 'http://localhost:3000/',
clean: true,
},
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
ui: 'ui@http://localhost:3001/remoteEntry.js',
},
shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
}),
],
};
Run both dev servers in separate terminals:
# In remote folder
npm start # or whichever script runs webpack serve
# In host folder
npm start
In the host’s src/index.js you can now import the remote button:
import Button from 'ui/Button';
function App() {
return (
Host App
);
}
export default App;
Open http://localhost:3000 in a browser. You should see the host heading and the button rendered from the remote.
Verification steps
- Open Chrome DevTools → Network tab. Reload the page. Look for a request to
http://localhost:3001/remoteEntry.js. Subsequent requests for the Button module should appear under that same origin. - In the Console, check for warnings about duplicate React versions. If federation is working, you should see only one instance of React loaded.
- Open React DevTools (if installed). The component tree should show a single React root, with the Button component appearing as a child of the host’s App.
Trade‑off / limitation
Module Federation introduces runtime overhead: the host must fetch the remote’s remoteEntry.js before it can resolve exposed modules. This adds a network round‑trip, which can affect initial load time if the remote is slow or unavailable.
More critically, shared dependencies must be version‑compatible. If the remote depends on React 17 and the host on React 18, the singleton: true setting will cause webpack to warn and may load two copies of React, defeating the purpose and possibly causing runtime errors. Teams should align peer‑dependency ranges or use a shared dependency management strategy (e.g., a locked package.json in a monorepo).
Actionable closing
Start small: expose a single UI component from a remote, configure the host to consume it, and verify that shared libraries appear only once. Once the basic flow works, gradually expose more modules and enforce dependency alignment through automated checks (e.g., npm ls or a CI step that compares versions). This approach lets teams develop independently while keeping the final bundle lean and consistent.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.