Microfrontend Architectures: The Flavors
The flavors of microfrontend composition — from server-side HTML to module federation — and how to pick the right one for your actual constraints.
When someone says "we use microfrontends," that sentence is nearly meaningless without more context. I've seen it describe everything from separate PHP pages to fully federated runtime module loading. They're all technically microfrontends. They have almost nothing in common.
Here's how I think about the actual options.
When Microfrontends Make Sense
First, the honest answer on when to reach for this: the most compelling reason I've seen is when rewriting the existing codebase would be untenable. Not because of architectural purity, or because the team read about it and wanted to try it — but because the legacy system is 15 years old and there's no clean path to replace it all at once.
You start peeling pieces off. Each extracted piece becomes independently deployable. Eventually you've migrated the core without a big-bang rewrite. That's a legitimate use of microfrontends.
The other cases are genuine team ownership needs — multiple teams shipping at different cadences, with genuinely distinct domains, where deploy coupling is measurable pain.
One thing I'd push back on: the idea that a single team would benefit from microfrontend architecture. Having one team maintain 4 CI/CD pipelines, coordinate npm publishes, wait for packages to propagate, just to then bump versions downstream — the burden of proof for that being worth it is very high. I haven't seen a case that clears it.
The Flavors
ExpandMicrofrontend composition patterns
Server-Side Composition
The old school approach: separate server-rendered pages, each owned by a different team. A PHP page here, a Rails page there, stitched together at the routing layer.
It sounds dated. It increasingly isn't. Island architecture (which we'll cover later) is a modern version of this idea — deliver HTML first, progressively enhance with JavaScript only where needed. Frameworks like Astro and SvelteKit's form actions are bringing this pattern back in a much more elegant form.
The practical case I keep thinking about: a settings dashboard I worked on that had turned into a complex SPA with elaborate validation state, data threaded across forms, a nightmare to maintain. The fix was embarrassingly simple — make each settings section a separate page. Only the data needed for that form on the page. HTML form that posts. Suddenly it was simple. That's server-side composition working exactly as intended.
Good fit: Content-heavy sites, compliance-heavy environments (banking, healthcare), cases where SSR is non-negotiable. Weak point: Doesn't solve the SPA case. If you have a complex interactive app, you're not living in this world.
Build-Time Integration
Separate packages, separate teams, but everything gets assembled into a single bundle at deploy time. The app shell pulls in all the remote packages as dependencies, builds them all together, ships one artifact.
What you get: atomic deploys (everything goes out together or nothing does), the ability to type-check across the full system, QA can test the integrated app before it ships.
What you give up: independent deploys. If Team A needs to go out and Team B has a broken build, nobody goes out. That's exactly the coordination problem people are trying to escape by moving to microfrontends.
The nuance I've come to appreciate: this isn't a bad trade if deploy coupling isn't your main pain. If you want code ownership and team autonomy over the codebase without needing to deploy independently, build-time composition might be the right spot on the spectrum.
Good fit: Teams that want code boundaries but can coordinate deploys. Catching integration errors before production. Weak point: None of the deploy autonomy. The whole point of runtime composition, you don't get.
iframes
Every team deploys their slice independently, each living in its own iframe. Total CSS isolation — no global styles leaking across boundaries. Each can deploy on any schedule with zero coordination.
The isolation is the entire value proposition, and it's genuine.
The cost is equally real. Cross-frame communication requires postMessage or Broadcast Channel. Responsive design becomes a multi-frame coordination problem — container queries help but it's still friction. Authentication state, routing, shared UI state — all of it requires explicit bridging.
I've vetoed this one consistently, mostly because the responsive design story has always been painful enough to outweigh the isolation benefits for the apps I've worked on.
Good fit: Situations where CSS isolation is critical and cross-frame communication needs are minimal. Weak point: Most things that make a web app feel integrated become hard: routing, shared state, responsive layout.
Runtime Module Federation (JS Modules)
This is what most people mean when they say microfrontends today. The shell loads, then lazily fetches remote JavaScript modules at runtime from wherever each team has deployed them.
Each team ships a bundle to their CDN or S3 bucket. The shell has a manifest of where to find each remote. At runtime, it imports them as JavaScript modules.
The upside: teams truly deploy independently. A remote team can ship at 3pm on a Friday (please don't), and the shell doesn't need to redeploy to pick up the change. That autonomy is real.
The downside: reliability becomes a function of every remote being available. With a monolith, either everything is deployed or nothing is. With runtime composition, any individual remote can be down or broken while the rest of the app continues — which sounds good until you're on-call and trying to trace which bucket has a broken policy.
This is what we'll dig into in depth — module federation with Rsbuild and React.
Good fit: Large organizations with mature teams, clear ownership, monitoring infrastructure, and genuine deploy independence needs. Weak point: Configuration complexity, runtime failures, dependency version coordination across teams.
Web Components
Each team ships their slice as a web component. CSS is encapsulated via Shadow DOM. Teams deploy independently. Works with any framework.
The server-side rendering story for web components is effectively nonexistent at the time of writing — there are experimental approaches, but nothing production-ready. If SSR matters to you, this path closes quickly.
If SSR isn't required, it's a genuinely interesting option: real isolation, framework-agnostic, independently deployable.
Good fit: Apps where SSR isn't needed, teams using different frameworks, or teams doing gradual framework migrations. Weak point: SSR story is weak, and the ecosystem maturity is behind React/Vue/Svelte equivalents.
The Pattern I Keep Coming Back To
None of these are pure choices in practice. The systems I've seen that work well are usually some mix — server rendering for the marketing and settings pages, a SPA shell for the core product, build-time composition for the design system, and maybe one or two runtime-federated pieces where teams genuinely need independent deploys.
The goal isn't to pick one pattern and apply it everywhere. It's to understand the trade-off each one makes and pick intentionally based on the actual pain.
Keep reading
Enjoyed this? Get more like it.
Deep dives on system design, React, web development, and personal finance — straight to your inbox. Free, always.