Article Guide
Architect16 min readJune 13, 2026

Migrating a Frontend Monolith to Micro Frontends

Learn how to migrate a frontend monolith to micro frontends using the strangler pattern, domain boundaries, shell integration, routing migration, shared design system, deployment safety, rollback, and observability.

Tags:
Micro FrontendsMigrationFrontend ArchitectureStrangler PatternSystem Design

Migrating a frontend monolith to micro frontends is not a simple refactor.

It is an architecture migration.

A bad migration creates:

textEditor
Two broken systems
Duplicate UI
Routing conflicts
Shared state chaos
Deployment risk
Team confusion
Runtime failures

A good migration is incremental, domain-driven, measurable, and reversible.

The goal is not to rewrite everything.

The goal is to safely extract parts of the monolith into independently owned frontend applications while keeping the product working for users.

This article explains how to migrate a frontend monolith to micro frontends using a practical, interview-ready approach.

1. The Starting Point: Frontend Monolith

Most products start with one frontend app.

Example:

textEditor
frontend-monolith
├── app
├── routes
├── pages
│   ├── home
│   ├── catalog
│   ├── product-details
│   ├── cart
│   ├── checkout
│   ├── orders
│   └── profile
├── shared
│   ├── ui
│   ├── hooks
│   ├── utils
│   └── api
└── state

This is normal.

A monolith is not automatically bad.

It becomes a problem when it starts blocking teams and releases.

2. Why Migrate?

Migration should have a clear business or engineering reason.

Good reasons:

textEditor
Multiple teams are blocked by one release cycle.
Build and test pipelines are too slow.
Ownership boundaries are unclear.
A single bug can delay full frontend release.
Teams need independent deployment.
The product has stable business domains.
The company wants incremental framework migration.

Weak reasons:

textEditor
Micro frontends are trendy.
A conference talk recommended it.
A big company uses it.
We want to use Module Federation.
We think splitting code automatically improves performance.

Strong interview phrase:

I would migrate to micro frontends only when team ownership and release independence justify the complexity.

3. Migration Goal

The migration goal should be specific.

Bad goal:

textEditor
Move frontend to micro frontends.

Good goal:

textEditor
Extract the catalog domain into an independently built and deployed remote while keeping the existing monolith stable.

Better:

textEditor
Reduce release coordination between Catalog and Checkout teams by extracting Catalog first, then Cart, then Profile, with rollback support at each step.

A migration should improve:

textEditor
Team ownership
Release independence
Build isolation
Deployment confidence
Runtime reliability
Observability

4. Do Not Big-Bang Rewrite

The biggest mistake is trying to rewrite the full frontend at once.

Bad migration:

textEditor
Stop feature development.
Create new shell.
Rewrite catalog.
Rewrite cart.
Rewrite checkout.
Rewrite profile.
Replace monolith after 12 months.

Problems:

textEditor
Huge delivery risk
No incremental value
Feature freeze pressure
Business changes during rewrite
Hidden bugs discovered late
Hard rollback

Better migration:

textEditor
Keep monolith running.
Extract one domain.
Compose new remote beside monolith.
Route small traffic.
Measure.
Stabilize.
Repeat.

This is the strangler approach.

5. The Strangler Pattern

The strangler pattern means gradually replacing parts of an old system with a new architecture.

For frontend migration:

textEditor
Existing Monolith
      │
      │ extract one domain
      ▼
Shell + Monolith + One Remote
      │
      │ extract next domain
      ▼
Shell + Multiple Remotes + Smaller Monolith
      │
      │ repeat
      ▼
Micro Frontend Platform

You do not remove the monolith immediately.

You slowly shrink it.

6. Migration Stages

A practical migration can follow these stages:

textEditor
Stage 0: Clean up monolith boundaries
Stage 1: Introduce shell/container
Stage 2: Extract first low-risk remote
Stage 3: Route traffic to remote
Stage 4: Add independent deployment
Stage 5: Add monitoring and rollback
Stage 6: Extract next domain
Stage 7: Retire monolith pieces

This avoids unnecessary risk.

7. Stage 0: Prepare the Monolith

Before extracting anything, improve the monolith structure.

If the monolith is a mess, extracting a remote will be painful.

Preparation steps:

textEditor
Organize code by domain.
Reduce circular dependencies.
Move shared UI into design system.
Separate domain APIs.
Add route-level code splitting.
Add tests around critical flows.
Define ownership boundaries.

Example target structure:

textEditor
src/
├── features/
│   ├── catalog/
│   ├── cart/
│   ├── checkout/
│   ├── profile/
│   └── orders/
├── shared/
│   ├── ui/
│   ├── utils/
│   └── config/
└── platform/
    ├── auth/
    ├── routing/
    └── analytics/

This may already solve many problems.

Sometimes after modularizing the monolith, micro frontends may not be needed yet.

8. Identify Domain Boundaries

Good boundaries are business-domain boundaries.

Good candidates:

textEditor
Catalog
Search
Product Details
Cart
Checkout
Orders
Profile
Marketing

Bad candidates:

textEditor
Button
Product image
Price label
Header text
One small filter component

Boundary checklist:

textEditor
Can one team own this area?
Can it be tested independently?
Can it be deployed independently?
Does it have clear route ownership?
Does it have limited communication with other areas?
Does it have its own API/domain model?
Can it fail without breaking the whole product?

If the answer is no, the boundary may be wrong.

9. Choosing the First Remote

Do not start with the riskiest domain.

Bad first extraction:

textEditor
Checkout
Payment
Authentication
Order placement

These areas are business-critical and failure-sensitive.

Better first candidates:

textEditor
Marketing landing page
Product listing page
Profile page
Order history page
Recommendations widget

A good first remote should be:

textEditor
Visible enough to prove value
Low enough risk to recover safely
Clear in ownership
Limited in cross-app communication
Not deeply tied to checkout/payment

Example first extraction:

textEditor
Catalog Remote

Why?

textEditor
Clear domain
Owns category/listing pages
Can be route-owned
High value
Usually safer than checkout

10. Introduce the Shell App

At some point, you need a shell or container.

The shell owns:

textEditor
Global layout
Top-level routing
Navigation
Authentication bootstrap
Remote loading
Error boundaries
Fallback UI
Feature flag bootstrap
Analytics initialization

During migration, the shell may compose both:

textEditor
Old monolith routes
New remote routes

Example:

textEditor
Shell App
├── /                         → Monolith Home
├── /categories/:slug         → Catalog Remote
├── /cart                     → Monolith Cart
├── /checkout                 → Monolith Checkout
└── /profile                  → Monolith Profile

The shell becomes the migration bridge.

11. Coexistence Architecture

architecture diagram
                    ┌──────────────────────┐
                    │      Shell App        │
                    │ Routing/Auth/Layout   │
                    └──────────┬───────────┘
                               │
          ┌────────────────────┴────────────────────┐
          │                                         │
          ▼                                         ▼
┌──────────────────────┐                 ┌──────────────────────┐
│ Existing Monolith     │                 │ Catalog Remote        │
│ Cart/Checkout/Profile │                 │ Product Listing       │
└──────────────────────┘                 └──────────────────────┘

This lets the team extract gradually.

The user should not feel the difference.

12. Routing Migration

Routing is one of the hardest parts.

Before migration:

textEditor
Monolith owns all routes.

During migration:

textEditor
Shell owns top-level route decisions.
Monolith owns old routes.
Remote owns extracted route subtree.

Example:

textEditor
/categories/:slug → Catalog Remote
/cart             → Monolith Cart
/checkout         → Monolith Checkout
/profile          → Monolith Profile

After more extraction:

textEditor
/categories/:slug → Catalog Remote
/cart             → Cart Remote
/checkout         → Checkout Remote
/profile          → Profile Remote

Important:

Deep links and refresh must work throughout migration.

Deep links must keep working.

Example:

textEditor
/categories/shoes?page=2&sort=price

Expected flow:

textEditor
User opens URL directly
      │
      ▼
Shell loads
      │
      ▼
Shell matches /categories/:slug
      │
      ▼
Catalog Remote loads
      │
      ▼
Catalog Remote reads query params
      │
      ▼
Products render

Do not rely on in-memory navigation state.

Use URL state for route-level state like:

textEditor
Search query
Filters
Sorting
Pagination
Category slug
Selected tab

14. Shared Design System Migration

Before extracting remotes, stabilize the design system.

If every part of the monolith uses different UI patterns, micro frontends will make inconsistency worse.

Design system migration steps:

textEditor
Define design tokens.
Create shared components.
Migrate common UI patterns.
Add visual regression tests.
Document usage guidelines.
Version the design system.
Make remotes consume it consistently.

Bad:

textEditor
Catalog remote uses its own buttons.
Cart remote uses another modal system.
Checkout remote invents new form components.

Good:

textEditor
All remotes use shared design tokens and approved components.

15. Shared State Migration

Shared state must be handled carefully.

Monoliths often have one global store.

Example:

textEditor
Redux Store
├── auth
├── catalog
├── cart
├── checkout
├── profile
└── orders

During migration, avoid exposing the whole store to every remote.

Better approach:

textEditor
Shell owns platform state.
Remotes own domain state.
Backend owns business-critical state.
StateRecommended Owner
Auth summaryShell/auth provider
ThemeShell/platform
LocaleShell/platform
Product filtersCatalog Remote + URL
CartCart API + Cart Remote
CheckoutCheckout API/session + Checkout Remote
OrdersOrders API + Orders Remote
ProfileProfile API + Profile Remote

Avoid migrating by simply sharing the old global store across all remotes.

That creates a distributed monolith.

16. API and Backend Migration

Frontend boundaries should align with backend/API ownership where possible.

Example:

textEditor
Catalog Remote → Catalog API
Cart Remote → Cart API
Checkout Remote → Checkout API
Profile Remote → Profile API
Orders Remote → Orders API

If the monolith currently uses one large API client, gradually split it.

Bad:

textEditor
Every remote imports one giant shared API client with all domains.

Good:

textEditor
Each remote owns or consumes its domain API client.

For complex aggregation, consider BFF patterns.

17. Communication During Migration

During coexistence, the monolith and remote may need to communicate.

Use explicit contracts.

Recommended:

textEditor
URL state
Backend state
Small custom events
Shell-mediated platform context

Avoid:

textEditor
Remote directly mutates monolith store.
Monolith imports remote internals.
Remote reads hidden localStorage keys from monolith.

Example:

textEditor
Catalog Remote emits product:viewed
Shell analytics records event
Monolith does not need to know remote internals

18. Deployment Migration

Initially, the extracted remote may deploy with the monolith.

But the final goal is independent deployment.

Migration steps:

textEditor
Step 1: Remote code extracted but deployed with monolith.
Step 2: Remote has separate build.
Step 3: Remote artifact published separately.
Step 4: Shell loads remote at runtime.
Step 5: Remote has independent CI/CD.
Step 6: Remote can be rolled back independently.

Do not claim success until independent deployment and rollback are working.

19. Remote Manifest During Migration

Use a manifest to control which remote version the shell loads.

Example:

jsonEditor
{
  "catalogApp": {
    "version": "1.0.0",
    "url": "https://cdn.company.com/catalog/1.0.0/remoteEntry.js",
    "owner": "catalog-team",
    "rollbackVersion": "0.9.8"
  }
}

Benefits:

textEditor
Controlled rollout
Rollback support
Environment promotion
Per-remote version visibility
Release governance

During migration, this gives safety.

20. Rollback Strategy

Every extraction must have a rollback plan.

Example:

textEditor
Catalog Remote v1.0.0 fails in production.

Rollback options:

textEditor
Route /categories back to monolith.
Update manifest to previous remote version.
Disable feature flag for remote route.
Show fallback UI.

The safest migration includes a route-level fallback.

Example:

textEditor
/categories/:slug
   ├── primary: Catalog Remote
   └── fallback: Monolith Catalog Page

Rollback must be tested before release.

21. Feature Flags for Migration

Feature flags help control migration risk.

Use flags for:

textEditor
Enable new catalog remote
Route percentage of users to remote
Enable new cart drawer
Enable new profile page
Disable broken remote quickly

Example:

textEditor
catalog_remote_enabled = true
catalog_remote_rollout_percentage = 10

Rollout plan:

textEditor
Internal users → 1% → 10% → 25% → 50% → 100%

Monitor at each stage.

22. Testing Migration

Migration requires extra testing.

Test:

textEditor
Old route still works.
New remote route works.
Deep links work.
Refresh works.
Auth works.
Shared layout works.
Analytics works.
Fallback to monolith works.
Remote loading failure is handled.
E2E critical journeys still pass.

Example E2E journey:

textEditor
Open category page
Apply filter
Open product
Add to cart
View cart
Checkout

If catalog is migrated but checkout is still monolith, the cross-boundary journey must still work.

23. Observability During Migration

You need to know whether the new remote is healthier than the old monolith route.

Track:

textEditor
Remote load success/failure
Remote runtime errors
Remote version
Route
Shell version
Fallback usage
Conversion impact
Web Vitals
API errors
User journey failures

Example:

jsonEditor
{
  "route": "/categories/shoes",
  "renderer": "catalogRemote",
  "remoteVersion": "1.0.0",
  "shellVersion": "2.4.1",
  "event": "remote_loaded"
}

Compare:

textEditor
Monolith catalog route performance
vs
Catalog remote route performance

Migration should be measured, not assumed successful.

24. Team Ownership During Migration

Migration fails without ownership.

Define:

AreaOwner
Shell appPlatform Team
Catalog extractionCatalog Team
Design systemDesign System Team
Routing migrationPlatform + Domain Team
CI/CD setupPlatform Team
MonitoringPlatform + Domain Team
Rollback processPlatform + Domain Team

Each remote should have:

textEditor
Code owner
Deployment owner
Monitoring owner
Incident owner
Documentation owner

No ownership means no accountability.

25. Migration Roadmap Example

A practical migration roadmap:

textEditor
Phase 1: Modularize monolith
Phase 2: Introduce shell routing layer
Phase 3: Extract Catalog Remote
Phase 4: Add manifest-based loading
Phase 5: Add monitoring and rollback
Phase 6: Extract Profile Remote
Phase 7: Extract Cart Remote
Phase 8: Extract Orders Remote
Phase 9: Extract Checkout Remote last
Phase 10: Retire old monolith routes

Checkout is extracted later because it is business-critical.

26. What to Extract First

Good first candidates:

textEditor
Marketing landing page
Catalog listing
Profile page
Order history
Recommendations widget

Poor first candidates:

textEditor
Checkout payment
Authentication
Order placement
Complex shared cart logic

Decision table:

CandidateFirst Extraction?Reason
Marketing pageGoodLow risk
Catalog pageGoodClear domain
Profile pageGoodClear ownership
CartMediumMore shared state
CheckoutPoor initiallyCritical flow
AuthPoor initiallyPlatform-critical

27. Common Migration Anti-Patterns

Anti-PatternWhy It Is Bad
Big-bang rewriteHigh risk and slow value
Extracting checkout firstToo risky
No rollback pathProduction incidents become severe
Sharing old global store everywhereCreates distributed monolith
No shell ownership modelRouting becomes chaotic
No design systemUI inconsistency grows
No contract testsRuntime breakage
No observabilityMigration health unknown
Too many tiny remotesOperational overhead
Migrating without business reasonArchitecture vanity

28. Interview Questions

Q1. How would you migrate a frontend monolith to micro frontends?

I would use an incremental strangler approach. First, I would modularize the monolith and identify domain boundaries. Then I would introduce a shell that can route to both old monolith pages and new remotes. I would extract one low-risk, high-value domain first, such as catalog or profile, deploy it independently, monitor it, and add rollback. After it stabilizes, I would repeat with other domains.

Q2. What would you extract first?

I would avoid extracting checkout or authentication first because they are critical flows. I would start with a domain that has clear ownership and limited coupling, such as catalog listing, profile, order history, or a marketing page.

Q3. How do you reduce migration risk?

Use feature flags, route-level fallback, contract tests, remote loading fallback UI, manifest-based versioning, canary rollout, monitoring, and rollback to monolith or previous remote version.

Q4. How do you handle shared state during migration?

I would avoid exposing the old global store to all remotes. Platform-level state like auth, theme, and locale can be provided by the shell. Domain state should live inside the owning remote or backend APIs. Business-critical state like cart and checkout should be backend-first.

Q5. How do you know the migration is successful?

The migration is successful when the extracted domain is independently owned, independently deployed, monitored, rollback-safe, integrated with routing/auth/design system, and no longer blocks or depends on the monolith release cycle.

29. Strong Senior Answer

If an interviewer asks:

“How would you migrate an existing frontend monolith to micro frontends?”

A strong answer:

I would not do a big-bang rewrite. I would use a strangler-style migration. First, I would clean up the monolith by organizing it around domains like catalog, cart, checkout, profile, and orders. Then I would introduce a shell app that owns top-level routing, auth bootstrap, layout, remote loading, and fallback UI. Next, I would pick one low-risk, high-value boundary such as catalog or profile. I would extract it as a remote, compose it beside the monolith, and route only that path to the new remote. I would use feature flags and a remote manifest so we can roll out gradually and roll back quickly. I would avoid extracting checkout or auth first because those are critical flows. I would also avoid sharing the old global store across remotes because that creates a distributed monolith. Instead, I would move toward URL state, backend-owned business state, and explicit event contracts. The migration is only successful when the extracted remote has independent CI/CD, contract tests, observability, clear ownership, and rollback support.

30. Final Migration Checklist

  • Is the domain boundary clear?
  • Does one team own it?
  • Is it low enough risk for first extraction?
  • Are route boundaries defined?
  • Do deep links work?
  • Is shared UI available through design system?
  • Is state ownership clear?
  • Are communication contracts documented?
  • Can the shell load the remote safely?
  • Is fallback UI available?
  • Is route-level rollback possible?
  • Are feature flags available?
  • Are contract tests in place?
  • Is monitoring per remote available?
  • Can the remote deploy independently?
  • Can it roll back independently?

31. Summary

Migrating from a frontend monolith to micro frontends should be incremental.

Do not rewrite everything.

Do not start with the riskiest flow.

Do not share the old global store everywhere.

A good migration follows this path:

textEditor
Modularize monolith
Identify domains
Introduce shell
Extract one remote
Route gradually
Monitor
Rollback if needed
Repeat
Retire old monolith pieces

The strongest takeaway:

A successful micro frontend migration is not measured by how much code was split. It is measured by whether teams can own, deploy, monitor, and roll back their domains independently.

References

  • Micro Frontends — Martin Fowler (https://martinfowler.com/articles/micro-frontends.html)
  • Micro Frontends (https://micro-frontends.org)
  • AWS Prescriptive Guidance: Micro-frontends (https://docs.aws.amazon.com/prescriptive-guidance/latest/micro-frontends-aws/introduction.html)
  • webpack Module Federation Documentation (https://webpack.js.org/concepts/module-federation/)
  • Module Federation Official Site (https://module-federation.io)