Micro-frontends are the architectural pattern of splitting a large web application into independently developed and deployed pieces. Module Federation (Webpack 5+, also in Vite/esbuild plugins) is the most common implementation. The interview tests whether you understand the tradeoffs and have done the architectural thinking — not whether you can wire up the build config from memory.
What problem does this solve?
For very large frontend organizations (50+ engineers, multiple teams, multiple products under one brand), monorepo + monolithic build runs into:
- Deploy coordination — every team’s changes ship together
- Build times that scale linearly with code size
- Coupling between teams that should be independent
- Hard to use different framework versions across teams
Micro-frontends address each of these — at significant complexity cost.
The architecture
One shell application owns the page chrome (header, footer, routing). Multiple remote applications are loaded into the shell at runtime.
Each remote is its own deployable. Owns its own build, deploy pipeline, and team. Exposes specific components or routes to the shell.
Module Federation basics
Webpack 5 introduced the ModuleFederationPlugin. Each app declares:
- name: a unique identifier
- exposes: which modules other apps can import
- remotes: which other apps to load at runtime
- shared: which dependencies to share (React, etc.)
At runtime, the shell loads the remote’s manifest, which lists exposed modules. The shell can then dynamically import any exposed module.
The shared dependency problem
Two remotes both bundle React. The user downloads React twice. Solutions:
- Mark React as shared with strict version matching
- The shell loads React; remotes import from the shell
- Singleton enforcement so two React copies do not exist
Mismatched versions silently break — useState from one React in a component rendered by another React, etc.
State sharing across micro-frontends
Three patterns:
- URL: route params, query strings — robust, simple
- Custom events: dispatchEvent on window with structured payloads
- Shared state container: a Redux/Zustand store loaded once and accessed by all remotes
Avoid global state by default — it couples micro-frontends and undoes much of the independence you gained.
Routing
The shell owns top-level routing. Remotes can have their own internal routing within their mounted area. Use the shell’s router; do not stack two routers.
When to adopt
Micro-frontends pay for themselves when:
- Multiple teams work in genuinely independent product surfaces
- Independent deploy cadence is a real requirement (not a nice-to-have)
- Build times are a real productivity bottleneck
- Different products legitimately need different framework versions or stacks
Don’t adopt when:
- You have one team and one product
- Your build times are tolerable
- You are pre-product-market-fit
Costs you should know
- Operational complexity — one outage now spans multiple deployables
- Cross-team coordination on shared dependencies and contracts
- Performance — extra HTTP requests, fragmented bundles
- Testing — integration testing across remotes is harder
- Onboarding — new engineers face more abstraction
Alternatives to consider first
- Monorepo with package boundaries: Nx or Turborepo. Logical separation without runtime split.
- Iframe-based isolation: for legacy or genuinely heterogeneous environments. Cruder but simpler.
- Server-side composition: SSR shell that includes content from other services. Different model but achieves similar goals.
Frequently Asked Questions
What companies use micro-frontends successfully?
Spotify, IKEA, Microsoft (parts of Office), DAZN, Zalando. All very large frontend organizations.
Is Module Federation the only option?
No. Single-spa is another major framework. Native ESM imports work too at smaller scale. Module Federation is the most adopted for Webpack-based stacks.
How do I version remotes?
Each remote deploys independently. The shell loads “latest” from the remote’s URL by default; for stability, pin to specific versions in production.