Micro-frontends promise team autonomy and independent deployments. But the costs are real and often underestimated. I’ve spent the last several years implementing and evolving a micro-frontend architecture at enterprise scale, and the reality is far more nuanced than the conference talks suggest.

The promise

The pitch is compelling: split your frontend into independently deployable units, each owned by a separate team. Teams move at their own pace. No more coordination bottlenecks. No more merge conflicts in shared code.

For large organisations with many frontend engineers spread across multiple product teams, these benefits are genuine. When the cost of coordinating deployments across a monolithic codebase exceeds the overhead of running a distributed system, micro-frontends start to make sense.

But “start to make sense” and “are obviously the right choice” are very different statements.

The hidden costs nobody warns you about

Consistency requires active engineering

In a monolithic frontend, visual consistency comes for free. Everyone imports from the same component library, uses the same CSS tokens, follows the same patterns. The moment you split into micro-frontends, consistency becomes something you have to actively engineer.

In practice, this means building a shared component system. We went with native web components for this, which turned out to be a strong choice because they work across any framework (or no framework at all). But even with shared components, you still need to enforce token usage, align on interaction patterns, and run visual regression testing across boundaries. None of this is free.

Bundle duplication is a real problem

Every micro-frontend that bundles its own copy of React, or its own copy of a design system library, adds weight to the page. I’ve seen setups where a single icon library was duplicated across five micro-frontends, adding hundreds of kilobytes of redundant code.

Module Federation solves some of this through shared dependency declarations. You can tell webpack that React is a singleton and should only be loaded once. But this creates its own complexity: version mismatches between shared dependencies can cause subtle runtime bugs that are extremely hard to track down.

The honest truth is that a well-built monolithic SPA will almost always have a smaller bundle than a micro-frontend setup serving the same page. The question is whether the organisational benefits outweigh that performance cost.

Developer experience takes a hit

This is the cost that teams underestimate the most. In a monolith, you clone one repo, run one dev server, and everything works. In a micro-frontend architecture, your local development setup needs to orchestrate multiple applications.

We solved this with a shell application that proxies to individual micro-frontends, but it took real engineering effort to make the local development experience acceptable. Hot module replacement across boundaries is tricky. Debugging issues that span multiple micro-frontends requires understanding the communication layer between them. New engineers face a steeper learning curve because they need to understand not just the code, but the architecture that connects the pieces.

Event-driven communication is powerful but complex

When micro-frontends need to talk to each other (and they always do), you need a communication mechanism. We chose an event-driven approach: micro-frontends emit events when something meaningful happens, and other micro-frontends can subscribe to those events.

This is a clean pattern that keeps micro-frontends loosely coupled. But it introduces a whole category of problems that don’t exist in a monolith: event ordering, event replay for late-loading micro-frontends, debugging event flows, and maintaining an event contract that all teams agree on.

You’re essentially building a distributed system in the browser. All the challenges of distributed systems apply: eventual consistency, message delivery guarantees, contract versioning. These are solvable problems, but they require deliberate architectural work.

Shared state is a design challenge

Authentication tokens, user preferences, routing state, analytics context. Anything that spans micro-frontend boundaries needs careful interface design.

The naive approach is shared global state, but that reintroduces the coupling you were trying to avoid. The correct approach is defining clear contracts between micro-frontends: what data is available, who owns it, and how changes propagate. This requires upfront design work and ongoing discipline.

When micro-frontends are worth the cost

After living with this architecture for years, here’s when I believe the tradeoffs are justified:

Multiple autonomous product teams that genuinely need independent release cycles. If your teams are deploying together anyway, micro-frontends add complexity without delivering their primary benefit.

A multi-brand platform where different brands need different features but share underlying infrastructure. Micro-frontends let you compose brand-specific experiences from shared building blocks.

A legacy migration path where rewriting everything at once isn’t practical. Micro-frontends let you strangle the old system incrementally, replacing sections while the rest continues running.

An organisation scaling beyond 30-40 frontend engineers where the monolith’s coordination costs have become the primary bottleneck. Below that threshold, a well-structured monorepo usually provides enough isolation.

When they’re not worth it

Your team has fewer than 20 frontend engineers. The overhead of micro-frontend infrastructure will slow you down more than the monolith’s coordination costs.

You’re using micro-frontends to avoid fixing your monolith’s architecture. If the real problem is poor code organisation, adding a distribution layer on top will make things worse, not better.

The primary motivation is “it’s what Netflix does.” Netflix has hundreds of frontend engineers. Their problems are not your problems.

You don’t have a shared component system in place first. Without shared components and design tokens, each micro-frontend will diverge visually, and your users will notice.

My recommendation

Start with a well-structured monorepo. Use clear module boundaries, CODEOWNERS for explicit ownership, and affected-only CI to keep build times fast. This gives you most of the organisational benefits of micro-frontends (clear ownership, modular code, team autonomy over their domain) without the distributed systems complexity.

Only reach for micro-frontends when the monorepo’s coordination costs genuinely become the bottleneck. You’ll know when that happens: deployments start blocking each other, teams are waiting days for reviews from other teams, and the build pipeline is so slow that engineers are losing meaningful time.

When you do make the move, invest in the foundation first: a solid communication layer, a shared component system (native web components are a strong choice here), and a local development experience that doesn’t make engineers miserable. The architecture is only as good as the developer experience it provides.