Micro-Frontend Platform Architecture
Why micro-frontends, and why they often fail
- Micro-Frontends
- Module Federation
- React
- Performance
- Platform Governance
- Product teams
- 06
- Load time reduction
- 30%
- Analytics dashboards
- 04
01
Why micro-frontends, and why they often fail
The decision to move toward a micro-frontend architecture was already made when I took on platform ownership. Six product teams, each with their own roadmap and deployment cadence, needed to ship features into a shared shell application without stepping on each other. The architectural direction made sense. The implementation details did not yet exist.
The failure mode I've seen in micro-frontend projects is that teams treat Module Federation as a solution rather than a mechanism. They federate everything, create deep runtime dependencies between remotes, and end up with something harder to reason about than a monorepo. The shell becomes a coordination point nobody owns and everybody blames.
02
The contracts that made independence possible
The first thing I established was a set of integration contracts - explicit agreements about what a remote could assume about its host environment and what it couldn't. Shared dependencies were declared at the platform level. Each team got a dependency governance document that specified which packages were singleton (one version, owned by the shell) and which were team-owned. React, React DOM, and the design system were singletons. Everything else was the team's business.
This sounds obvious but it prevented a class of runtime errors that had already burned teams before I standardized it. When two remotes load different versions of React into the same page, you get hooks errors that are nearly impossible to debug if you don't know what you're looking for.
Beyond dependencies, I defined mounting contracts - every remote had to expose a standard mount and unmount interface, accept a configuration object at startup, and clean up its own event listeners and subscriptions on unmount. Teams owned their internal state. The shell owned routing and the global auth context.
03
Performance was a design decision, not an afterthought
One of the tensions in micro-frontends is that independent bundles can mean duplicated code and slower load times. I addressed this through a combination of shared module deduplication (handled by the Federation configuration) and route-level lazy loading in the shell. Remotes were only loaded when a user navigated to a route that required them. The shell kept its own bundle lean - no business logic, no domain code, just routing and authentication.
The 30% load time reduction we saw across the analytics dashboards came from a few specific changes: route-level code splitting so the initial load only fetched what the landing route needed, React Suspense boundaries that let partial UI render while data fetched, and asset delivery optimization including preloading of likely next-navigation remotes based on route adjacency. Core Web Vitals (LCP, CLS) were tracked in CI - a build that regressed LCP beyond threshold failed the pipeline.
04
Governance without gatekeeping
The harder problem was keeping teams aligned without creating a bottleneck. I didn't want every architectural decision to require my review. What I built instead was a lightweight RFC process - teams proposed changes to shared contracts, integration patterns, or singleton dependencies through a written document. Reviews happened asynchronously. I focused my attention on changes that crossed team boundaries or touched platform-owned surfaces.
Architecture Decision Records captured the reasoning behind each major choice. When a new engineer joined a team and asked "why do we do it this way," the ADR had the answer, including the alternatives considered and why they were ruled out. That is documentation that actually gets used because it explains reasoning, not just outcomes.
Comparison
Dashboard load time improvement
Relative load time after route-level code splitting, suspense boundaries, and optimized asset delivery.
- Before optimization
- Baseline
- After optimization
- 30% reduction
Diagram
Platform ownership boundaries
Integration contracts kept platform responsibility clear while teams remained independently deployable.
01
Shell
Routing and global authentication
02
Contracts
Singleton dependencies and lifecycle interface
03
Remotes
Team-owned features and state