Large organisations use it to compose apps from separately deployed micro-frontends.
Module Federation (webpack 5's ModuleFederationPlugin) lets one build load modules from another build at runtime. Each build can be:
- A remote: it
exposesmodules ("./Button": "./src/Button.jsx") and emits a smallremoteEntry.jsmanifest. - A host: it declares
remotes(checkout: "checkout@https://cdn.example.com/checkout/remoteEntry.js") and imports them like packages:import("checkout/Cart").
Shared dependencies are negotiated at runtime: with shared: { react: { singleton: true, requiredVersion: "^19.0.0" } }, host and remotes use one copy of React instead of each bundling their own. Version mismatches produce warnings or fall back to separate copies, depending on settings.
Practical rules:
- The host must load shared modules asynchronously, so use a bootstrap pattern:
index.jscontains onlyimport("./bootstrap"), giving the runtime time to negotiate shared versions. Otherwise you hit "Shared module is not available for eager consumption". - Treat exposed modules as public APIs with versioning and contracts; independent deploys mean independent failures. Wrap remote loads in error boundaries.
- Type sharing, dynamic remotes and runtime plugins come from Module Federation 2.0 (
@module-federation/enhanced), which also works with Rspack, Vite and other bundlers.
Federation solves organisational problems (independent deploys of a large frontend) at the price of runtime complexity; a monorepo with shared packages is often simpler.
Example
// shell app (host): webpack.config.js
const { ModuleFederationPlugin } = require("webpack").container;
module.exports = {
entry: "./src/index.js", // contains only: import("./bootstrap");
output: { publicPath: "auto" },
plugins: [
new ModuleFederationPlugin({
name: "shell",
remotes: {
checkout: "checkout@https://cdn.example.com/checkout/remoteEntry.js",
},
shared: {
react: { singleton: true, requiredVersion: "^19.0.0" },
"react-dom": { singleton: true, requiredVersion: "^19.0.0" },
},
}),
],
};
// checkout app (remote): new ModuleFederationPlugin({ name: "checkout",
// filename: "remoteEntry.js", exposes: { "./Cart": "./src/Cart.jsx" }, shared: {...} })
// in the shell: const Cart = React.lazy(() => import("checkout/Cart"));Remember
- Remotes expose modules via remoteEntry.js; hosts import them at runtime
- Shared singletons keep one copy of React across builds
- Use an async bootstrap entry for shared modules
- Federation trades runtime complexity for independent deploys
Watch out for
- Importing shared modules eagerly in the host entry, causing 'not available for eager consumption'.