09 · Micro Frontends (MFE)
Applying microservice-style decomposition to the frontend: independently developed, tested, and deployed UI pieces owned by autonomous teams, composed into one product. Current as of Module Federation 2.0 (
@module-federation/enhanced, cross-bundler) and Native Federation (import-maps/ESM, framework-agnostic).
Positioning
MFEs are an organizational solution before they’re a technical one. The driver is team autonomy and independent deployment at scale, not raw performance — performance is usually a cost you manage, not a benefit you gain. If your whole app is built by a handful of people, a modular monolith is almost always better. The honest senior take: reach for MFEs when you have many teams whose release cadences collide in a monolith.
Foundations: the core principles
(From Cam Jackson’s canonical martinfowler.com article and Luca Mezzalira’s work.)
- Independent deployability — each MFE ships on its own pipeline without coordinating releases.
- Team ownership — a team owns a vertical slice (UI → BFF) end to end.
- Technology agnosticism (in principle) — teams could use different frameworks; in practice most standardize for sanity.
- Isolation — failures, styles, and runtime of one MFE shouldn’t break another.
- Resilience — the page degrades gracefully if one MFE fails to load.
Deep dive
1. Composition models (the central design axis)
Where are the pieces stitched together?
- Build-time composition — MFEs published as npm packages, assembled at build. Simple, but kills independent deployment (you must rebuild the shell to ship a change). Generally an anti-pattern for true MFEs.
- Server-side composition — a server/edge assembles fragments into the HTML (SSI/ESI, Tailor/Podium, or Next Multi-Zones). Good for SEO and first paint; the fragment is the unit.
- Edge-side composition — ESI at the CDN.
- Client-side / run-time composition — the shell loads remotes in the browser at runtime. The dominant modern approach; Module Federation and Native Federation live here. Maximizes independence.
- Iframe — maximal isolation (separate documents, CSS/JS sandboxed), worst integration (routing, sizing, shared state, accessibility, performance). Mostly legacy; occasionally right for hard security boundaries (embedding untrusted third-party UIs).
A second axis is routing: a single-SPA-style shell/orchestrator routes between MFEs, or each MFE owns a path prefix.
2. Module Federation (the workhorse)
Introduced in Webpack 5; the breakthrough is runtime code sharing: a host can load remote modules over HTTP at runtime, and they can share dependencies (one React instance, not five) negotiated at load time.
- Roles: a build exposes modules (
exposes) as a remote and/or consumes others as a host (remotes). A build can be both. - Shared dependencies:
shared: { react: { singleton: true, requiredVersion } }ensures a single React/runtime across MFEs — critical, because two React copies break hooks/context. - Module Federation 2.0 (
@module-federation/enhanced) adds: TypeScript type sharing across remotes (type-safe remote imports — MF1’s biggest pain), manifest-based dynamic discovery (resolve remotes at runtime from a manifest, not hardcoded URLs), runtime plugins, preloading, and a unified runtime that works across Webpack, Rspack, and Vite (@module-federation/vite). MF1.0 is deprecated; use 1.5+ or 2.0. - Rspack (Rust, webpack-compatible API, 5–10× faster builds) is now the common fast migration path for MF2 (
14).
3. Native Federation
A framework-agnostic alternative (Manfred Steyer / Angular community, but not Angular-specific) built on web standards: import maps + native ESM + top-level await, no Webpack lock-in. Works with Vite/esbuild. Trades some of MF’s runtime sophistication for standards-based simplicity and bundler independence. The “federate via the platform” philosophy.
4. The hard parts (where MFEs actually cost you)
- Shared dependencies & version skew — the central problem. Multiple React versions = broken hooks; duplicate libraries = bloated bundles; mismatched design-system versions = visual drift. Singletons + version negotiation + a shared baseline help, but governance is the real fix.
- Styling isolation — Team A’s
.buttonmust not restyle Team B’s. Solutions: CSS Modules, Shadow DOM (04), CSS-in-JS with scoping, or strict BEM/utility conventions. Global CSS is the enemy. - Cross-MFE communication — MFEs should be loosely coupled. Prefer: custom events / a typed event bus / pub-sub (
04) over shared mutable state; URL as shared state; a thin shared context for cross-cutting concerns (auth/theme). In 2026 practice, a secure message bus (validated, even cryptographically-signed messages) is recommended over direct state sharing so one team can’t corrupt another’s state. - Shared state — generally avoid. If unavoidable, use a tiny framework-agnostic store (Nanostores,
06) or expose state via events, not a giant Redux shared across teams. - Routing — who owns the URL? Usually the shell owns top-level routing and delegates sub-paths; deep-linking and back/forward must work across MFE boundaries.
- Design-system distribution — a shared design system must NOT be imported-and-shared by the host (that re-couples everyone to one version and makes the host a UI app). Better: each MFE owns/consumes the design system independently, or the design system is itself a remote for enterprise platforms. Keep the host a runtime platform, not a UI platform.
- Performance — duplicated runtimes, multiple framework copies, and many network round-trips. Mitigate with shared singletons, preloading, and HTTP/2/3 multiplexing (
18). - Observability & debugging — errors span deployments; you need distributed tracing, source maps per remote, and per-MFE error boundaries.
- Consistent UX & accessibility — independent teams drift on patterns, focus management, and a11y unless a shared design system + guidelines enforce consistency (
11,19).
5. The single-spa lineage
single-spa is the framework-agnostic orchestrator that predates Module Federation’s popularity: it manages lifecycles (bootstrap/mount/unmount) of multiple SPAs (even mixed React/Vue/Angular) on one page, with import maps for loading. Often combined with Module Federation. Good when you must mix frameworks.
6. When NOT to use MFEs
- Small/single team → modular monolith (clear module boundaries, one deploy) wins.
- Tightly-coupled UI with lots of shared state → the coordination cost eats the autonomy benefit.
- SEO-critical with tight performance budgets and no team-scale problem → the overhead isn’t worth it.
The decision is Conway’s Law-driven: adopt MFEs when your org structure (many independent teams) makes a monolith’s shared release pipeline the bottleneck.
Worked example: a Module Federation host + remote (conceptual)
// remote (team "checkout") — exposes a mountable widget, shares React as a singleton
new ModuleFederationPlugin({
name: 'checkout',
filename: 'remoteEntry.js',
exposes: { './CartWidget': './src/CartWidget' },
shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
});
// host (the shell) — discovers the remote (ideally via manifest), lazy-loads it
new ModuleFederationPlugin({
name: 'shell',
remotes: { checkout: 'checkout@https://checkout.app/remoteEntry.js' },
shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
});
// in the shell, at runtime:
const CartWidget = React.lazy(() => import('checkout/CartWidget'));
// wrap in <Suspense> and an error boundary so a failed remote degrades gracefully
The checkout team deploys CartWidget independently; the shell picks it up at runtime without rebuilding. The singleton React is what keeps hooks/context working across the boundary. The error boundary is what delivers resilience principle #5.
Pitfalls & gotchas
- Multiple React instances → broken hooks/context. Enforce singletons; verify at runtime.
- The host importing the design system and sharing it → re-couples all teams to one version; keep the host a runtime platform.
- Global CSS leakage → use CSS Modules/Shadow DOM/scoping.
- Sharing a big mutable global store across teams → recreates the monolith’s coupling; prefer events/URL/tiny agnostic stores.
- Hardcoded remote URLs → no independent deploy/rollback; use manifests (MF2).
- No graceful degradation → one failed remote blanks the page; wrap every remote in an error boundary + fallback.
- Adopting MFEs without the org problem → all cost, no benefit.
- Version skew drift in shared libs and design tokens → governance, contract tests, automated dependency alignment.
Interview / self-test questions
- What problem do MFEs actually solve, and when are they the wrong choice? (Team autonomy/independent deploy at scale; wrong for small teams or tightly-coupled UI.)
- Compare build-time, server-side, edge, and run-time composition.
- How does Module Federation share dependencies, and why is
singleton: truefor React essential? - What does Module Federation 2.0 add over 1.0? (TS types, manifest discovery, runtime plugins, preloading, cross-bundler runtime.)
- Module Federation vs Native Federation — mechanism and tradeoffs.
- How should MFEs communicate and share state? (Events/typed bus/URL; avoid shared mutable global state.)
- How do you isolate styles across MFEs?
- Why should the host not own/share the design system directly?
- How do you keep the page resilient if a remote fails to load?
- How does Conway’s Law inform the MFE decision?
Recommendations
- Adopt MFEs only when team-scale autonomy is the real problem; otherwise build a modular monolith.
- Use Module Federation 2.0 (Rspack/Webpack/Vite) or Native Federation; never hardcode remotes — use manifests.
- Enforce shared singletons (one React/runtime) and a shared design system consumed per-MFE, not shared by the host.
- Communicate via a typed, validated event bus / URL, not a shared mutable store.
- Isolate styles (CSS Modules / Shadow DOM); wrap every remote in an error boundary + fallback.
- Invest early in governance (version alignment, contracts, distributed tracing) — it’s where MFEs live or die.
- Keep the shell a thin runtime platform.
Books & references
- “Micro Frontends” — Cam Jackson, martinfowler.com. The canonical defining article (composition models, the demo, the principles).
- “Building Micro-Frontends” — Luca Mezzalira (O’Reilly). The standard book; decisions framework, architectures, governance.
- micro-frontends.org — Michael Geers’ site and his book “Micro Frontends in Action” (Manning).
- Module Federation docs (module-federation.io) — MF2,
@module-federation/enhanced, Rspack/Vite runtimes. - Native Federation (Manfred Steyer / angular-architects) — the import-maps/ESM approach.
- single-spa docs (single-spa.js.org) — framework-agnostic orchestration.
- “State of React/JS” + ThoughtWorks Technology Radar — adoption signal and cautions on MFE overuse.
Connections
13-microservices-and-orchestration.md— MFEs are the UI mirror of microservices; Conway’s Law applies to both.12-bff-and-data-enrichment.md— each MFE team often owns its BFF (the “vertical slice”).11-design-systems.md— the shared design system that keeps MFEs visually/behaviorally consistent.04-the-web-platform.md— custom events, Shadow DOM, import maps, Web Components as MFE substrate.14-build-tools-and-bundlers.md— Webpack/Rspack/Vite federation tooling.08-nextjs-and-meta-frameworks.md— Next Multi-Zones / federation as a shell.