Lessons Learned in Migrating to Micro-Frontends

2026-07-2311 min read

Luca Mezzalira, principal solutions architect at AWS, presents micro-frontends as an organizational and architectural choice, not a technique for loading UI components at runtime. Drawing on migrations he supported at Amazon, AWS, and customer organizations, he offers a sequence for moving from a frontend monolith without prematurely committing to a framework.

These notes summarize Mezzalira's claims and examples from his QCon San Francisco 2025 presentation, published by InfoQ on July 14, 2026. The section explicitly labeled Synthesis is my interpretation; the other sections report the presentation.

The talk presents the migration as six steps: establish a decision framework, review common architectures, identify boundaries, take one micro-frontend end to end, add a second micro-frontend, and assess where AI can help. The headings below regroup those steps by topic without changing their sequence.

The Outcome Comes Before the Tool

Mezzalira says teams often begin by choosing Single-SPA, Module Federation, Web Components, or Next.js multi-zones. That is too early: a team cannot select the composition mechanism sensibly before it knows why the system should be distributed or where its boundaries belong.

The outcomes he associates with a justified migration are:

  • Team autonomy: a team can own and release its area without waiting for other teams.
  • Organizational scalability: adding a team does not reduce the throughput of existing teams.
  • Agility: modular boundaries make it easier to change one business area as external conditions change.
  • Fast flow: smaller, independent releases shorten the feedback loop with users.

These are business and organizational properties rather than frontend-library features. Mezzalira cautions that micro-frontends are not a silver bullet and should be used only when a distributed system is actually needed. A distributed backend does not, by itself, require a distributed frontend.

He frames the system as sociotechnical: architecture, organization, and engineering culture constrain one another. Even a repository decision changes more than source control. His monorepo example assumes short-lived branches, a healthy trunk, disciplined sharing, and engineers able to manage those constraints. The point is not that a monorepo is wrong, but that an architectural decision cannot be copied independently of its operating context.

A Micro-Frontend Is Not a Runtime Component

The presentation contrasts components and micro-frontends by what they optimize:

Component Micro-frontend
Reuse and UI consistency Independence and fast flow
Receives context and behavior from its container Owns its domain context and behavior
Reduces duplication Reduces dependencies between teams
Can be as small as a button Is normally coarse-grained and self-contained

A reusable button deliberately exposes configuration so many domains can use it. Its container knows how to place it in context. An independently deployable micro-frontend instead needs enough UI, behavior, and domain knowledge to operate as a self-contained unit. Treating every component loaded at runtime as a micro-frontend preserves container coupling while adding distributed-system complexity.

The practical consequence is that reuse is not free. Sharing can couple release cadences, ownership, and domain assumptions. Mezzalira therefore rejects eliminating duplication at any cost.

The Decision Framework

Before selecting technology, the presentation asks teams to make a connected set of decisions.

1. Choose the Composition Role

A client-rendered system commonly has an application shell. Mezzalira gives it four responsibilities: configuration, authorization, composition, and routing. It should remain domain-agnostic and should not require a release each time a micro-frontend changes. He also recommends keeping it technology-agnostic where possible rather than binding it to the framework used by the first slice.

A server-rendered system may instead use a UI composer to combine HTML fragments before returning a page. The application shell generally carries more browser-side behavior than the composer.

2. Choose Vertical or Horizontal Boundaries

  • A vertical micro-frontend owns one view or a related group of views.
  • A horizontal micro-frontend occupies part of a view alongside other micro-frontends.

The two can coexist, and boundaries can become finer as a system matures. Mezzalira recommends starting with coarse vertical slices because they are easier to identify, release, observe, and roll back. Horizontal composition adds coordination and communication inside a page, so it should not be the default.

3. Choose Where Rendering and Routing Happen

Rendering changes the whole architecture. Mezzalira associates server-side rendering with public experiences such as e-commerce, where performance and organic discovery matter, and client-side rendering with authenticated or device-hosted applications. These are contextual heuristics, not universal rules.

Client-side routing typically belongs to the application shell. Server-side routing can happen at the edge, an API gateway, or a load balancer. Only after making these decisions does the presentation discuss implementations such as Module Federation, Single-SPA, Web Components, Native Federation, Piral, Next.js multi-zones, and Astro Server Islands.

4. Minimize Cross-Boundary Communication

Vertical slices usually communicate less because related behavior stays within one domain. For horizontal slices, Mezzalira lists custom events, an event emitter, and reactive streams, expressing a preference for the simpler, loosely-coupled event-emitter model.

He argues against a shared global state across micro-frontends because changing the shape of shared data can force broad retesting. One customer, he reports, spent four months removing such a design without shipping features. Persistent cross-view data can live in cookies, browser storage, or preferably an API; ephemeral identifiers can travel in the query string. The goal is to keep data and behavior encapsulated rather than creating a new distributed monolith.

Common Deployment Shapes

For a client-rendered application, the presentation favors storing static JavaScript, CSS, and HTML in object storage behind a CDN rather than operating containers solely to serve static files. Mezzalira reports that a previous company scaled from 10,000 to 2 million users in one minute with this model. That is his case-study observation, not a general capacity guarantee.

For server rendering, he describes several shapes:

  • Route first-level paths such as /products, /myaccount, and /checkout to independently operated server-rendered applications.
  • Load deployable pages from object storage at runtime so changing a page does not require redeploying the surrounding infrastructure.
  • Use a composer, as in his simplified description of BBC News, to retrieve and combine independently rendered HTML fragments, with caches protecting upstream services.

He also separates edge routing and caching from edge rendering. If the data remains in one region or data center, rendering globally at the edge may add developer complexity without removing the long trip to the data. In that case, regional rendering plus CDN caching may be the better trade-off.

Find Boundaries from Behavior, Then Start Coarse

Mezzalira recommends looking at first-level URLs, user journeys, domain ownership, traffic, and code volatility rather than dividing a page by its visible widgets. Factors that change together tend to integrate; factors with different rates of change or ownership may be candidates for separation.

At DAZN, he used traffic and the customer journey to identify an initial set of vertical slices: landing, onboarding, catalog plus video player, account, and help/FAQ. Sign-in and sign-up were separated only later, after the onboarding area created real friction. Similarly, if one of ten payment methods changes frequently while nine remain stable, his advice is to consider two units, not automatically create ten.

This is an argument against speculative granularity. Start with fewer, coarser units and split when observed change patterns or team coordination costs justify it.

Migrate One Slice End to End

The first micro-frontend should pass through the complete delivery lifecycle: design, development, testing, deployment, and production observation. Although small, this slice forces the team to solve the reusable parts of the migration: the shell or composer, routing between old and new systems, CI/CD, testing, rollback, and telemetry.

If the chosen boundary is horizontal, his advice is to build and release all the new horizontal slices needed for that view together. Mixing old and new implementations within one view makes rollback harder and leaves temporary integration code that becomes costly as the number of slices grows.

Mezzalira's heuristic for a good application shell is stability: after roughly a year, it should change infrequently because it contains little domain logic. That is a design signal, not a service-level target.

For incremental cutover, he applies the Strangler Fig pattern at the CDN:

  1. Keep the legacy application as the default destination.
  2. Route a selected path, such as the catalog, to the new micro-frontend.
  3. Canary the new version for a percentage of users or a selected country or browser.
  4. Store route selection in external configuration or a service.
  5. Roll back by changing that configuration rather than redeploying code.

The migration cost is a hard navigation when an absolute URL moves a user between the legacy and new applications. Mezzalira considers that temporary trade-off preferable to writing extensive integration code that will be discarded after the migration.

He reports that teams using this routing safety net moved from three deployments per month for each micro-frontend to 25 deployments per day per team. The talk does not provide the measurement period or a controlled comparison, so this should be read as his experience, not as a predicted result for every organization.

Add the Second Slice Deliberately

The second micro-frontend exposes concerns that one isolated slice does not:

  • Use a design system for visual consistency, but introduce shared libraries and components intentionally.
  • Accept cheap duplication when the alternative is governance and a possibly incorrect abstraction. Citing Sandi Metz's warning that duplication is cheaper than the wrong abstraction, Mezzalira says his former team duplicated a header because it had changed only once in three years.
  • Treat multiple frameworks as a temporary migration condition, not an architecture to optimize. He calls this optimization "micro-frontends anarchy," referring to a term from the 2017 Thoughtworks Technology Radar. Different frameworks bring different release lifecycles, tooling, and development models, not just different renderers.
  • Distinguish global routing between micro-frontends from local routing owned within one micro-frontend.
  • Consider a backend for frontend (BFF) when a complex view needs data from several backend domains. One frontend team can then own the view and its BFF instead of requiring several teams to coordinate inside one page.

Mezzalira says authentication and authorization can usually be centralized in the application shell, leaving micro-frontends free of that responsibility. He also reports that some financial systems decentralize authorization so each micro-frontend performs its own check; he presents this as a contextual exception, not a blanket requirement for regulated systems.

What AI Can and Cannot Help With

Mezzalira does not delegate domain boundaries to AI. The relevant context is often tacit and absent from tickets or documentation, so he recommends human-led event storming or domain story mapping.

He sees narrower uses for AI in dependency-management configuration and architectural fitness functions. His examples include generating Dependabot configuration, enforcing an illustrative 30 KB micro-frontend bundle budget, and using ts-arch (a TypeScript port of Java's ArchUnit) to detect forbidden imports across boundaries. The 30 KB figure is his example of an enforceable budget, not a recommended universal limit. Conversely, he recounts an incompatibility between React Router and Module Federation where AI generated layers of abstraction instead of identifying that downgrading React Router fixed the problem.

His broader observation is that assistants work better with co-located code than with modularization and long sessions where context decays. His own workflow is to co-locate code first and then modularize it explicitly. He also warns that AI can increase cognitive load by adding and deleting large amounts of code. His advice is to state the intended architecture, ask for and refine a plan before implementation, and iterate in small steps.

Synthesis: A Migration Readiness Checklist

The following checklist is my synthesis of the presentation, not a checklist shown by Mezzalira:

  1. Write down the required organizational outcome and verify that a modular monolith cannot meet it more cheaply.
  2. Map domain ownership, user journeys, URLs, traffic, and rates of change before naming any framework.
  3. Select rendering, composition, routing, and communication models as one coherent design.
  4. Begin with one coarse vertical slice and preserve the legacy application as the fallback.
  5. Make routing configuration reversible, then test a small audience before a broad cutover.
  6. Deliver and observe the first slice end to end before creating a platform for hypothetical future slices.
  7. Add sharing only where measured change frequency and ownership make it cheaper than duplication.
  8. Encode boundary and bundle constraints as fitness functions so independence does not erode silently.

The central lesson is not "adopt micro-frontends." It is to earn distribution by connecting each boundary to independent ownership and delivery, then grow the architecture through reversible steps. Without that connection, runtime composition can reproduce the monolith's coupling in a more operationally expensive form.


Canonical source: Luca Mezzalira, “Lessons Learned in Migrating to Micro-Frontends,” InfoQ