Back to System Design
System Design
hard
staff

How do independent deploys work in a micro frontend setup?

Split the UI into apps owned by different teams that deploy independently. Composition: server-side stitching, build-time integration, runtime (Module Federation), or iframes. Independent deploys solve org coordination, not technical performance. The costs — bundle duplication, shared-state coupling, cross-app navigation, design consistency — are real; only adopt when org structure demands it.

10 min read·~20 min to think through

Micro-frontends are an org structure answer to a deployment problem, not a frontend perf answer. If 80 engineers across 6 teams are blocked on a shared build, MFE removes the blocker. If you have a 10-engineer team, you're paying complexity costs with no payoff.

What makes them "micro"

Each frontend is:

  • Owned by one team.
  • Deployed independently — its release doesn't require coordinating with other apps.
  • Built and tested independently — separate pipelines, separate dependencies (within reason).
  • Composed at runtime, server time, or build time into a single user experience.

The four composition models

1. Build-time integration.

Each MFE is an npm package; the shell app installs them and bundles together.

ts
@org/checkout, @org/catalog → installed in shell → built into one bundle

Pros: simplest, full type safety, single bundle to optimize. Cons: not really independent — shell rebuilds and redeploys for any MFE update. Often called "modular monolith with package boundaries" — fine if you don't need independent deploys.

2. Server-side composition.

ts
Edge / SSR server fetches HTML from each MFE service, stitches into one response.

Examples: Server-Side Includes (SSI), Edge Side Includes (ESI), Cloudflare Workers, custom SSR composer. Each MFE is a separate service rendering a fragment of HTML.

Pros: independent deploys, SEO-friendly, low client cost. Cons: server complexity, cross-MFE interactivity needs glue.

3. Runtime via Webpack Module Federation.

js
// shell webpack config
new ModuleFederationPlugin({
  remotes: {
    checkout: "checkout@https://cdn/checkout/remoteEntry.js",
    catalog: "catalog@https://cdn/catalog/remoteEntry.js",
  },
});

// shell code
const Checkout = React.lazy(() => import("checkout/CheckoutApp"));

Each MFE publishes a manifest (remoteEntry.js) listing exposed modules. Shell loads them on demand at runtime. Shared dependencies (React, design system) are deduplicated via the shared config.

Pros: true runtime independence, shared deps, lazy. Cons: build coupling (versions must agree), runtime errors when a remote is unavailable, complex CI/CD.

Vite-based equivalents (@originjs/vite-plugin-federation) exist and are catching up; Webpack remains the most mature.

4. iframes / Web Components.

Each MFE is an independent app, embedded via <iframe> or a Web Component. The browser provides the boundary.

Pros: strongest isolation (CSS, JS, security, crashes don't propagate), works across frameworks (one MFE in React, another in Vue). Cons: navigation, deep linking, shared state, focus management are painful.

The hard problems

1. Shared dependencies. React loaded 4× = bundle bloat AND broken hooks (two React instances = "invalid hook call"). Module Federation's shared config dedupes; iframes can't share by definition; server composition doesn't have the problem.

2. Design consistency. Six teams each "improve" the button. Solve with: shared design system as a federated/published package; visual regression tests run per MFE against the system version; design ownership separate from MFE ownership.

3. Cross-MFE state. Auth, cart, current user — needed everywhere. Options: shared state via a small global event bus (window.postMessage for iframes, a custom event bus for federation), shared context propagated from shell, or each MFE re-fetches via a shared API client. Pick shared API + cache over shared state object — less coupling.

4. Routing. Who owns the URL? Typical: shell owns top-level routes; each MFE owns its subtree. Cross-MFE navigation goes through a router abstraction so MFEs don't import each other's routes directly.

5. Versioning. Shell deployed today expects checkout@2 exports; checkout deploys v3 with breaking changes. Either lock version pins (negating "independent") or treat the contract between shell and remote as a versioned API.

6. Bundle size. Naive composition ships every MFE's vendor chunks. Federation shared config + a shared CDN for design system are essential. Budget per MFE; CI fails if exceeded.

7. Observability. A bug in checkout shows up in a session that touched 4 MFEs — need to correlate. Shared error reporter (Sentry) with MFE name as a tag.

8. Local dev. Working in checkout means running shell + checkout + sometimes others. Either compose locally (slower DX) or stub the shell.

When MFE is the right answer

  • Independent teams shipping at different cadences (daily vs weekly vs quarterly).
  • Legacy + new — embedding new React app inside a legacy Angular/jQuery shell during migration.
  • Acquired companies — incorporating their existing app without rewrite.
  • Regulatory boundaries — a payments page deployed under stricter controls than the marketing site.

When MFE is the wrong answer

  • "We want code separation." → modular monolith, feature slices.
  • "We want faster builds." → better tooling (Turbopack, Vite, build caching).
  • "We want React + Vue." → pick one; "freedom" isn't worth the cost.
  • "Our app is big." → code-split + lazy-load, not micro-frontend.

2026 reality check

The early-2020s hype has cooled. Many teams that adopted MFE rolled back to modular monolith (single repo, single deploy, feature-bounded packages). The pattern that survived: MFEs at the org boundary, e.g., the marketing site, the docs site, the support portal, and the app — each owned by a different team, deployed independently. Within each, modular monolith.

Senior framing. The interviewer is checking whether the candidate (1) understands the org reason for MFE, (2) names the trade-offs honestly, (3) can articulate when NOT to use it, (4) names a specific composition strategy with its specific costs. The "we use Module Federation" answer is shallow; the "we use Module Federation because we have three teams shipping at different cadences, and we accept the bundle-share + versioning costs" is senior.

Follow-up questions

  • Module Federation vs iframes — when does each win?
  • How do you keep design consistent across MFEs?
  • Why is shared state across MFEs an anti-pattern?
  • When would you migrate from MFE back to a modular monolith?

Common mistakes

  • Adopting MFE to solve performance — wrong tool.
  • Allowing each MFE to ship its own React version.
  • Coupling MFEs via shared state objects.
  • Cross-MFE imports that defeat the independence claim.

Performance considerations

  • Bundle duplication unless shared deps are properly deduped.
  • Runtime composition costs ~one extra fetch per remote.
  • iframes have isolated rendering but heavier memory footprint per frame.

Edge cases

  • A remote is unreachable — shell must degrade gracefully.
  • Auth cookies across subdomains — need consistent domain scoping.
  • Cross-MFE focus management — keyboard nav across iframes is broken by default.

Real-world examples

  • Spotify (early adopter), IKEA's web, Zalando's Project Mosaic. Many large e-commerce sites still use MFE at the section level (account, checkout, catalog).

Related questions