Frontend Architecture ·Jan 2026 ·20 min read ·6.8K reads

Migrating to Micro-Frontends:
The Intuit Experience

How we migrated a monolithic React app serving 100M+ users to independently deployable micro-frontends — without a single minute of downtime, using the Strangler Fig pattern.

BG
Balachandraiah Gajwala
Senior Software Engineer (SDE3) · Intuit, Bengaluru
85%
Deploy time reduced
12
Teams unblocked
100%
Uptime maintained

The Problem with Our Monolith

By mid-2024, our QuickBooks frontend was a 4-year-old React monolith with 380+ components, 12 product teams committing to the same repo, and a build time of 22 minutes. A single bad merge could block all 12 teams from deploying. CI became our biggest bottleneck — not our engineers.

When your CI pipeline becomes a bottleneck for 12 teams and your build time exceeds 20 minutes, it's time to rethink your architecture — not just your build config.

Why Webpack 5 Module Federation?

We evaluated three approaches before committing: iframes (too isolated, poor UX), npm packages (too coupled, slow propagation across teams), and Webpack 5 Module Federation — the clear winner. Module Federation lets each MFE expose components that the Shell App loads at runtime, meaning no redeployment of the shell is needed when a child MFE ships a fix.

The Shell + MFE Architecture

// shell/webpack.config.js — Host application
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'shell',
      remotes: {
        billing: 'billing@https://mfe-billing.intuit.com/remoteEntry.js',
        reports: 'reports@https://mfe-reports.intuit.com/remoteEntry.js',
        tax:     'tax@https://mfe-tax.intuit.com/remoteEntry.js',
      },
      shared: {
        react:      { singleton: true, requiredVersion: '^19.0.0' },
        'react-dom': { singleton: true, requiredVersion: '^19.0.0' },
      },
    }),
  ],
};

// billing/webpack.config.js — Remote MFE
new ModuleFederationPlugin({
  name: 'billing',
  filename: 'remoteEntry.js',
  exposes: {
    './BillingDashboard': './src/BillingDashboard',
    './InvoiceList':      './src/InvoiceList',
  },
  shared: { react: { singleton: true } },
});

The Migration Phases

Phase 01 — Months 1–3
Strangler Fig Pattern
We didn't rewrite anything. New features were built as MFEs while the monolith continued serving existing routes. Each month, one section was extracted. Zero-downtime, zero big-bang rewrite.
Phase 02 — Months 4–6
Shared Design System + Event Bus
Published @intuit/shared-ui to internal npm registry. Built a lightweight event bus for cross-MFE communication to avoid prop-drilling across boundaries.
Phase 03 — Months 7–8
Per-MFE CI/CD Pipelines
Each MFE got its own GitHub repo, its own Jenkins pipeline, and its own deployment cadence. Billing team went from 1 deploy/week to 8 deploys/day. Biggest cultural win of the project.

Cross-MFE Communication

// shared/event-bus.ts — Lightweight cross-MFE events
export const eventBus = {
  emit: (event: string, data: unknown) =>
    window.dispatchEvent(
      new CustomEvent(event, { detail: data, bubbles: true })
    ),

  on: (event: string, cb: (data: unknown) => void) => {
    const handler = (e: Event) => cb((e as CustomEvent).detail);
    window.addEventListener(event, handler);
    return () => window.removeEventListener(event, handler);
  },
};

// Usage: Billing MFE notifies Shell of payment success
eventBus.emit('payment:success', { invoiceId: 'INV-001', amount: 1200 });

// Shell listens and updates global nav badge
eventBus.on('payment:success', (data) => updateNavBadge(data));

Pitfalls We Hit (And How We Fixed Them)

The Strangler Fig pattern is the safest way to migrate a large production system. Never do a big-bang rewrite when you're serving 100M+ users — extract incrementally and validate at each step.

The Numbers After Full Migration

Micro-Frontends Module Federation React 19 Next.js 15 Webpack 5 Intuit