Skip to content
Micro Frontends (MFE)

Architecture & Scale

Listen 0%
Speed

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.)

  1. Independent deployability — each MFE ships on its own pipeline without coordinating releases.
  2. Team ownership — a team owns a vertical slice (UI → BFF) end to end.
  3. Technology agnosticism (in principle) — teams could use different frameworks; in practice most standardize for sanity.
  4. Isolation — failures, styles, and runtime of one MFE shouldn’t break another.
  5. 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 .button must 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

  1. 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.)
  2. Compare build-time, server-side, edge, and run-time composition.
  3. How does Module Federation share dependencies, and why is singleton: true for React essential?
  4. What does Module Federation 2.0 add over 1.0? (TS types, manifest discovery, runtime plugins, preloading, cross-bundler runtime.)
  5. Module Federation vs Native Federation — mechanism and tradeoffs.
  6. How should MFEs communicate and share state? (Events/typed bus/URL; avoid shared mutable global state.)
  7. How do you isolate styles across MFEs?
  8. Why should the host not own/share the design system directly?
  9. How do you keep the page resilient if a remote fails to load?
  10. 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

Frontend Deep-Dive Library · content is the single source of truth.