A shell application must host four areas owned by four teams of five to eight engineers - admin, reporting, billing and support. Each area is a distinct route tree and no screen mixes two areas. All four use the same framework major version and the same design system. Releases today go out weekly on a shared train and the teams want to ship their own area independently. Which composition mechanism fits?
Show the full answer Hide the answer
The deciding property
Not team count, and not how modern the mechanism is. One fact settles it: does any single screen contain code from two teams?
Here the answer is no - four disjoint route trees, no mixed screens. When ownership follows URLs, composition is a routing problem, and routing is the cheapest composition there is. Runtime module loading exists for the case this problem does not have.
Why route-level splitting wins here
Each area becomes its own build, deployed to its own prefix, with a path router at the edge sending /admin/* to one origin and /billing/* to another. Deploy independence is total and rollback is per area, because nothing is resolved in the user's browser.
The price is honest and small: crossing areas costs a full document load instead of a client-side route change, commonly 200-600 ms extra on mobile, and each build ships its own framework runtime - roughly 45 KB compressed - and its own design-system slice. That duplication is paid once per area per session and then served from the HTTP cache, not per navigation.
Why the other options fail
- Module federation with a shared singleton. The right answer when fragments share a screen, or when teams genuinely cannot agree on a framework version. Here it buys nothing the edge router does not, and it charges for it: module existence, export shape and shared-dependency versions are now resolved in the user's browser, a remote you do not own cannot be rolled back by you, and the shared singleton reinstates exactly the coordinated upgrade the teams are trying to escape - one framework major bump becomes a four-team window.
- Versioned packages plus a shell rebuild. Correct when the areas are libraries consumed by one application. As a deploy mechanism it recreates the coupling: shipping requires someone else's build, so the shell lags, and the familiar end state is a median consumer several versions behind with a token change taking weeks to land everywhere.
- One build with four deploys a day. Defensible at two or three teams and genuinely simpler. It fails on rollback granularity: deploying the shell deploys everyone's code, so one team's bad change reverts three other teams' work. Build time is the complaint teams voice; blast radius is the one that actually hurts.
What would flip the decision
| If this changes | Choose | Because |
|---|---|---|
| A screen composes two teams' widgets | Runtime composition | there is no URL to split on |
| A team must stay on an older framework major | Federation or an iframe | you need two runtimes on purpose |
| Users cross areas on most sessions | One build | you are paying document loads on the hot path |
| Teams drop from four to two | One build | the coordination being removed costs less than two pipelines |
| An area must render inside a partner's page | Federation or a web component | the host is not your router |
When not to split the shell
Until independent deployment is a measured constraint rather than an aspiration. The evidence is specific: releases blocked waiting on another team, rollbacks that revert unrelated work, a queue on the shared train. With four teams on one weekly train, that evidence usually exists. With two teams it usually does not, and splitting then buys a second pipeline, a second observability surface and a slower cross-area navigation in exchange for nothing.