Micro-frontends: make the complexity earn it

Splitting a frontend across independently deployed applications solves a real problem, and it is an organisational one. If you cannot name the team boundary it follows, you are buying the cost without the benefit.

A micro-frontend architecture splits one user-facing application into several independently built and deployed pieces, assembled in the browser. It solves a genuine problem. That problem is almost never technical, and mistaking it for a technical one is how teams end up with all of the cost and none of the payoff.

The problem it actually solves

In a single application, everyone shares one deployment. Ten teams merging into one pipeline means the slowest test suite sets everyone's release cadence, one team's regression blocks nine others, and coordinating a release becomes a scheduling exercise. Nothing here is a code problem. It is a queueing problem.

Splitting the frontend lets each team own a deployable unit and ship on its own schedule. That is the benefit, and it is worth a lot at the right size.

Which means the honest test is a question about your organisation: how many teams are blocked on one deploy pipeline today? If the answer is one, you are considering distributed-system complexity to solve a problem you do not have.

Micro-frontends are an answer to a deployment-coordination problem. If deployment coordination is not hurting you, they are pure cost.

What you pay

The costs are predictable and worth reading before committing.

The shared-dependency one is the most consequential and the least discussed up front. It is the point where the architecture's promise of independence meets the reality that you are still shipping one page to one browser.

The contract is the whole design

Everything that goes wrong in these systems goes wrong at the boundary. The interface between host and remote is the actual architecture, and it deserves the same rigour as a public API.

Keep it narrow. A remote that needs fifteen props and four callbacks is not independent, it is a tightly coupled component with a network boundary in the middle, which is the worst of both designs. Version it explicitly, so a remote can evolve without an instant break. Define what happens when a remote fails to load, because it will, and 'the page is blank' is a decision you make by not making it.

Above all, resist shared mutable state across the boundary. The moment host and remote read and write the same store, you have recreated a monolith with worse tooling and no compiler checking the seams.

The middle ground people skip

Most of the deployment-independence benefit is available without runtime composition, and the options in between deserve consideration before you commit.

A monorepo with proper build caching gives you independent test and build steps per package while keeping one deploy and one dependency tree. Route-level splitting, where separate applications own separate URL paths with no shared runtime, gives real independence with a far simpler contract, at the cost of a full page load when crossing a boundary. Plain lazy-loaded route bundles handle the performance argument entirely, which matters because performance is often the reason offered and rarely the reason that holds.

Runtime composition is the heaviest tool available. It is the right one when several teams genuinely need to ship parts of the same page on different schedules. It is the wrong one when a monorepo and better caching would have solved it.

If the reason you are reaching for this is bundle size, you want code splitting. If it is team autonomy, keep reading.

The test worth applying

Before adopting this, write down which team owns which piece, and which deploy is currently blocking which other one. If the boundaries follow real team ownership and the blocking is real, the complexity is buying you something specific and you can measure whether you got it.

If you cannot fill that in without inventing hypothetical future teams, the answer is a monorepo with good caching. You can always split later, and splitting a well-modularised application is far easier than merging a distributed one back together.