Micro-Frontends Grew Up: Module Federation Works Across Vite, Webpack, and Rollup Now — But Is the Complexity Tax Still Too High?

I have been evaluating micro-frontends for the past three months, and I want to share what I found because the landscape has changed significantly since the last time most people looked at this.

Module Federation Is No Longer Webpack-Only

The biggest shift: Module Federation has evolved from a Webpack 5 plugin into a cross-bundler standard. As of early 2026, it works with Vite, Webpack, Rollup, and Rspack through the Module Federation 2.0 project. This is a meaningful change because it removes the bundler lock-in that was one of the strongest arguments against micro-frontends.

The promise is compelling: independently deployable frontend modules owned by different teams. Team A deploys their dashboard module on Tuesday, Team B deploys their settings module on Thursday, and the shell application composes them at runtime. No coordinated releases. No monolithic build pipeline. No “your PR is blocking my deploy.”

The Maturity Curve

Micro-frontends have followed a pretty clear maturity curve:

  • 2020: Experimental. A few brave companies (IKEA, Spotify) tried it. Lots of blog posts about the concept, very little production tooling.
  • 2023: Adopted by large companies. Webpack Module Federation stabilized. Real production stories emerged — both success and horror stories.
  • 2026: Supported by tooling. Cross-bundler federation, better dev tooling, TypeScript support across boundaries. The technical barriers are mostly solved.

That last point is important: the technical barriers are mostly solved. The question is no longer “can we do micro-frontends?” but “should we?”

Our Situation

My company has 5 frontend teams and 1 monolithic React application. Every team’s code lives in the same repo, builds in the same pipeline, and deploys as a single artifact. The pain is real:

  • A CSS change by one team breaks layouts in another team’s module
  • Deployments require a full build and regression test across all modules
  • A single flaky test in Team C’s code blocks Team A’s hotfix
  • Teams cannot adopt new libraries or patterns without coordinating with everyone else

Micro-frontends would solve the deployment coupling. Each team builds and deploys their module independently. The shell app loads them at runtime.

What Module Federation Solves

Federation handles the hard parts of runtime composition:

  • Shared dependencies: React, React DOM, and your design system library are loaded once and shared across modules
  • Independent builds: Each module has its own build pipeline, CI/CD, and deployment schedule
  • Version negotiation: If Module A needs React 18.3 and Module B needs React 18.2, federation can resolve this at runtime (in theory)
  • Type safety: Module Federation 2.0 supports TypeScript type extraction across module boundaries

What It Does NOT Solve

Here is where my evaluation got sobering:

Shared state management. User authentication, global application state, feature flags — these cut across module boundaries. Module Federation gives you runtime composition, not state management. You still need a solution for “the user just logged out, and all modules need to know.” Most teams build custom event buses or use a shared state library, both of which add their own complexity.

Consistent design. This is my biggest concern as a design systems lead. When each module is built independently, keeping visual consistency requires extraordinary discipline. Different teams diverge on spacing, color usage, component patterns. Without a strong design system and enforcement mechanism, your app starts looking like a Frankenstein of different design languages.

Routing. Navigating between modules without full page reloads is surprisingly hard. Deep linking, browser history, and URL state all need coordination between the shell and modules. This is solvable but it is not solved by Module Federation itself.

Debugging. When something breaks at a module boundary — a shared dependency version mismatch, a missing exported component, a type error that only manifests at runtime — the debugging experience is poor. Error traces do not cross federation boundaries cleanly.

The Complexity Tax

My evaluation found that Module Federation adds significant operational complexity:

  • Shared dependency management: Keeping React, your design system, and other shared libraries compatible across independently deployed modules requires active coordination. The “independence” is partial.
  • Version compatibility: Module A deploys with a new shared library version. Module B has not updated yet. Runtime errors for users who get the mismatch.
  • Runtime errors from type mismatches: TypeScript types are extracted at build time but modules deploy independently. Types can drift between modules.
  • Integration testing: Unit tests pass for each module. Integration tests need a running shell with all modules loaded. This testing surface is large and flaky.

My Decision

After three months of evaluation, I am recommending a monorepo with shared libraries and parallel builds instead of micro-frontends.

Specifically: Turborepo with package-based module boundaries, shared design system package, and parallel CI pipelines per package. Each team owns their packages. Builds are fast because Turborepo caches aggressively. Deployments are still coordinated (single artifact), but with good caching the build time is under 3 minutes.

This gives us:

  • Team ownership and clear boundaries (packages, not runtime modules)
  • Shared design system enforced at build time, not runtime
  • Standard debugging tools that work across the entire app
  • Type safety checked at build time, not runtime

We lose independent deployment, but for 5 teams, I do not think the complexity tax of runtime federation is worth the independence benefit.

The Question

Has anyone here adopted micro-frontends in production? At what scale did the complexity become worth it? I am genuinely curious whether there is a team size or deployment frequency where the calculus flips.

Maya, your evaluation mirrors my experience almost exactly — and I say this as someone who actually worked with micro-frontends in production for two years.

At my previous company (a fintech with about 60 frontend engineers across 8 teams), we adopted Module Federation in 2023. The deployment independence was genuinely great. Before micro-frontends, our release cycle was every two weeks because coordinating 8 teams into a single deployable artifact was a scheduling nightmare. After micro-frontends, each team deployed 2-3 times per week independently. That velocity improvement was real and meaningful.

But the developer experience was mixed at best.

Debugging was the biggest pain point. When something broke at the module boundary — and it broke regularly — the error traces disappeared across the federation boundary. You would see “ChunkLoadError: Loading chunk remote_entry failed” in Sentry with no useful stack trace pointing to the actual cause. Was it a network issue? A version mismatch? A missing export? You had to manually inspect the remote entry manifest, check deployment timestamps across modules, and correlate logs from different module pipelines. What should have been a 10-minute debug session became a 2-hour investigation.

Shared state was our Achilles heel. We built a custom event bus for cross-module communication — a lightweight pub/sub system where modules could publish events (user logged out, feature flag changed, theme updated) and other modules could subscribe. It worked for about 6 months. Then it became its own source of bugs: race conditions when multiple modules published simultaneously, memory leaks from unsubscribed listeners, and cascading failures when a subscriber threw an error. We spent more time maintaining the event bus than we would have spent on any coordinated deployment process.

The “shared dependency” promise was half-true. Module Federation’s singleton sharing worked for React itself. But our design system library had breaking changes every few weeks (as design systems do), and keeping all 8 modules on the same version was essentially the same coordination problem we were trying to avoid. We ended up with a “shared dependency upgrade day” once a month — which is just coordinated deployment with extra steps.

For teams under 50 engineers — honestly, for teams under 100 engineers — I strongly recommend the monorepo approach Maya described. Turborepo or Nx with package-based boundaries gives you 80% of the organizational benefits (team ownership, clear APIs between modules, parallel CI) without any of the runtime complexity. The deployment coordination cost at that scale is manageable. The debugging simplicity alone is worth it.

The calculus flips only when your monorepo build time exceeds your deployment SLA, and you have teams that genuinely need to deploy on independent schedules measured in hours, not days. For most companies, that threshold is somewhere north of 100 frontend engineers.

I am going to push back slightly here because I run micro-frontends in production and they work — but with important caveats.

We have 3 frontend teams and 3 federated modules: the core platform shell, a data visualization module, and an integrations marketplace module. We adopted Module Federation about 18 months ago and I would make the same decision again. But I want to be precise about why.

The reason micro-frontends work for us is that our modules have genuinely different deployment cadences. The integrations marketplace team ships 4-5 times per week because they are onboarding new third-party integrations constantly. The data visualization team ships once a week because their changes involve complex chart rendering that needs careful QA. The platform shell ships every 2 weeks because it is the most stable layer. Forcing all three teams into the same deployment schedule was creating real organizational friction — the marketplace team was waiting for visualization QA, and the visualization team was being pressured to cut QA short.

That said, I agree with Maya that micro-frontends are worth it only at sufficient scale or with sufficient deployment divergence. My threshold is not purely about team count. It is about two questions:

  1. Do your teams genuinely need to deploy on different schedules? Not “it would be nice” but “the current coordinated schedule is causing measurable problems” — delayed features, blocked hotfixes, teams waiting on each other.

  2. Are you willing to invest in the infrastructure? Micro-frontends are not a library you install. They are an architecture you maintain. You need: a shared dependency management strategy, an integration test pipeline that runs all modules together, monitoring for cross-module errors, and a design system team that enforces consistency.

For teams below that threshold — and I would set it at roughly 5 independent frontend teams with meaningfully different deployment needs — the monorepo with Turborepo or Nx is the right call. You get team ownership, parallel builds, and clear module boundaries without the runtime complexity.

The most important lesson I have learned: invest in a strong design system and shared component library BEFORE splitting into micro-frontends. We made the mistake of starting federation and design system work in parallel. For the first 6 months, each module looked subtly different — different button heights, different modal patterns, inconsistent loading states. Users noticed. It undermined trust in the product.

Now our design system is a shared federated module itself — it is loaded by the shell and available to all modules at runtime. Design token changes propagate to all modules on the next load. Component updates require each team to test against the new version before it goes live. This works well but it was hard to get right, and it would have been much easier to establish the design system in a monorepo first and then extract it into a federated module.

My advice: if you are considering micro-frontends, start with the monorepo approach Maya recommends. Get your build times fast, your team boundaries clean, and your design system mature. Then, if and only if you hit genuine deployment coordination problems, extract individual modules into federated remotes. This incremental path is much safer than a big-bang migration to micro-frontends.

Coming at this from the mobile side, and I see the exact same pattern playing out in native apps — which might give some useful perspective on where this ends up long-term.

In mobile, the equivalent of micro-frontends is “micro-apps” or “super-app architecture”: independently deployable modules within a single native application. WeChat pioneered this with mini-programs. Grab uses it across their ride-hailing, food delivery, and payments modules. At Uber, we have a variant of this for our driver and rider apps — different teams own different feature modules that are composed at build time (not runtime, which is a key difference from web micro-frontends).

The pattern works at super-app scale — we are talking 50+ teams, hundreds of engineers, apps that are essentially platforms hosting dozens of independent experiences. At that scale, the alternative (a monolithic codebase where every team touches the same build) is genuinely unworkable. Build times would be measured in hours. A single team’s bug blocks everyone. Module boundaries are not optional, they are survival.

But here is the insight that I think applies directly to Maya’s evaluation: the “shared shell + independent modules” pattern works, but the integration testing burden is massive.

At Uber, we estimate that roughly 30% of our mobile testing effort goes to integration testing between modules. Not testing individual module behavior — that is straightforward. Testing the interactions between modules. Does the ride module correctly hand off to the payments module when a trip ends? Does the navigation module correctly update when the driver module changes route? Does the authentication module correctly propagate session state to all other modules?

These are not edge cases. They are the core user experience. And they fail in subtle ways because runtime composition introduces emergent behavior that does not exist when you test modules in isolation. Module A works perfectly. Module B works perfectly. Module A + Module B together has a race condition in their shared event handling that only manifests under specific timing conditions.

The complexity tax is real, but in mobile the tax is primarily in testing, not development. Development is actually faster because teams are independent. But ensuring that independently developed modules compose correctly at runtime requires dedicated integration infrastructure: synthetic test environments that load all module combinations, canary deployments that roll out module updates to a small percentage of users first, and observability that can trace user sessions across module boundaries.

For web micro-frontends, I would expect the same pattern. Maya’s concern about debugging is the symptom. The underlying problem is that runtime composition is inherently harder to test than build-time composition. In a monorepo, your test runner has access to the entire application graph. In a federated architecture, each module’s test runner only sees its own code. The gap between those two is where bugs live.

My recommendation aligns with Michelle’s: start with build-time composition (monorepo) and only move to runtime composition when build-time composition becomes the bottleneck. And when you do move, budget heavily for integration testing infrastructure. If you are not willing to invest 25-30% of your engineering effort in testing the module boundaries, micro-frontends will create more problems than they solve.