Micro-frontends promised to bring the benefits of microservices to the frontend — independent deployability, team autonomy, technology flexibility. The reality has been more nuanced. Here's what we've learned from decomposing large React monoliths into micro-frontend architectures using Module Federation.
Why Micro-Frontends
The case for micro-frontends is strongest when you have multiple teams working on a single frontend application. When Team A's deployment blocks Team B, when merge conflicts are a daily occurrence, when the build takes 20 minutes — these are signals that your frontend has outgrown its architecture.
Micro-frontends solve the organizational problem first. The technical benefits are secondary.
Module Federation: The Enabling Technology
Webpack 5's Module Federation changed the micro-frontend landscape. Before Module Federation, micro-frontend implementations required complex build tooling, runtime composition frameworks, or iframe-based isolation. Module Federation makes it possible to share code between independently deployed applications at runtime.
How It Works
Each micro-frontend is a separate webpack build that exposes components or modules. The host application (shell) loads these exposed modules at runtime from their respective deployment URLs. Dependencies can be shared to avoid loading React multiple times.
The Shell Application
The shell is responsible for routing, authentication, and loading the appropriate micro-frontend for each route. It should be as thin as possible — business logic belongs in the micro-frontends, not the shell.
What We Got Right
Independent Deployability
Each team can deploy their micro-frontend independently without coordinating with other teams. This was the primary goal, and Module Federation delivers it cleanly.
Team Autonomy
Teams own their micro-frontend end-to-end — from design to deployment. This reduces coordination overhead and increases accountability.
Incremental Migration
We migrated a 200K line React monolith to micro-frontends incrementally over 18 months. The shell loaded the new micro-frontends alongside the legacy monolith. Users never experienced a big-bang migration.
What We Got Wrong
Shared State Management
Global state that spans micro-frontends is genuinely hard. We initially tried to share a Redux store across micro-frontends — this created tight coupling that defeated the purpose of the architecture. The solution was to push shared state into the shell and communicate through events.
Dependency Version Conflicts
When Team A uses React 17 and Team B uses React 18, you have a problem. Shared dependencies must be carefully versioned. We now enforce a shared dependency manifest that all teams must align on.
Performance Overhead
Loading multiple JavaScript bundles from different origins adds latency. We mitigated this with aggressive prefetching and by co-locating micro-frontends on the same CDN. But the performance overhead is real and must be measured.
Design System as Foundation
A shared design system is non-negotiable for micro-frontend architectures. Without it, each team builds their own components and the user experience becomes inconsistent. The design system should be a separate package that all micro-frontends consume as a dependency.
When Not to Use Micro-Frontends
Micro-frontends add significant complexity. For small teams (under 10 engineers), a well-structured monolith is almost always the better choice. The organizational benefits only materialize when you have multiple teams that need to work independently.
If your primary motivation is technical rather than organizational, reconsider. The complexity cost is high.






