Micro frontends are an organizational idea that ships wearing an architecture diagram. The pitch is fair: several teams, one product surface, releases that do not wait on a single queue. The usual result is a shell of separately built fragments, each with its own idea of a user, a route, and a button, meeting for the first time in production.
The runtime is rarely the thing that fails. Module federation, iframes, and web components can all be made to work. The failure happens earlier, when the boundary is drawn before anyone can say what is allowed to cross it.
Cut along the contract
Team ownership is a real constraint, and it is a bad line to cut the product along by itself. Two teams that share a user journey will share state whether the repository admits it or not. If the cut ignores that, the shared state comes back as custom events, duplicated fetches, and query parameters that only make sense if you were in the original Slack thread.
A boundary worth keeping has a contract. The routes it owns. The data it accepts. The events it emits. The design decisions it is not invited to reopen. Independent deploy is what you get once that contract is boring. Before that, independent means each team can break the page on a schedule of their own.
What stays shared
A few concerns are platform even when every feature team wants them to be local.
- Routing has one owner. Deep links still resolve when a fragment is lazy, and the shell knows very little about its children, and that little is written down.
- Identity is settled once. Session, tenant, and permissions are not a research project inside each fragment.
- The design language is shared at the token layer, and at the primitive layer when the surface is one product. Copied component folders diverge within a quarter.
- A trace follows a click across the seam. If you cannot follow a failure across the boundary, you own half of a screenshot.
Contract
The shell is a product
Someone has to own composition. How one team's empty state sits beside another team's table. What the page does when one fragment is down. How it loads when three of them are slow. That work is platform work. A shell treated as a thin index file lets the slowest fragment set the experience, with nobody accountable for the sum.
Degradation belongs in that layer. A fragment that fails should fail inside its slot, with the rest of the task still usable. A fragment that arrives late should not move the page after someone has already started reading it.
Keep the monolith modular when you can
Teams that already share a release cadence, a design system, and a build measured in minutes can get ownership from package boundaries inside one application. Enforce those boundaries with lint and the dependency graph. Let teams own directories. Review the public exports. You can still deploy together and still know who is on the hook for an incident.
Split a surface when the contract is real and the shared release is the actual pain. That pain can justify the runtime tax. A conference talk that made independence sound like maturity cannot. The year after that split is often spent rebuilding the monolith out of network calls, with extra latency.
One question settles most of these debates for me: can a new engineer ship a change across two areas without learning two build systems? When the answer is no, the complexity has only moved.
