Micro-frontends — independent deploys
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.
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.
@org/checkout, @org/catalog → installed in shell → built into one bundlePros: 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.
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.
// 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).