๐Ÿ—๏ธ
Frontend

Micro Frontend Architecture at Scale

AK
Arun Kataria
๐Ÿ“… December 12, 2024โฑ 6 min read
โ† Back to all posts

I've led micro frontend migrations at two large product companies. Both were complex, both had painful moments, and both taught me things I wish I'd known going in.

This post covers the real challenges: module federation config, shared state pitfalls, team coordination, and what I'd do differently.

โœ… What you'll learnWebpack Module Federation setup, shared state strategies across MFEs, avoiding version conflicts, deployment independence, and team ownership models.

Why Micro Frontends?

At organization with a massive monolithic React app. 40+ engineers touching the same codebase meant slow builds (14 min), risky deployments, and feature teams blocked by each other constantly.

The goal was simple: let each team own, build, and deploy their slice of the UI independently.

Module Federation โ€” The Setup That Actually Works

// Host app webpack.config.js
new ModuleFederationPlugin({
  name: 'host',
  remotes: {
    taxWizard: 'taxWizard@https://cdn.xyz.com/tax-wizard/remoteEntry.js',
    dashboard:  'dashboard@https://cdn.xyz.com/dashboard/remoteEntry.js',
  },
  shared: {
    react: { singleton: true, requiredVersion: '^18.0.0' },
    'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
    'react-redux': { singleton: true },
  },
})
๐Ÿ’ก Key rule: always set singleton:true for ReactIf two MFEs load different React instances, you'll get cryptic hook errors that are incredibly hard to debug. Singleton mode prevents this.

The Shared State Problem

This is where most teams get it wrong. You have two options:

Option A โ€” Shared Redux Store (what we tried first)

We exposed a shared Redux store from the host. It worked initially but created tight coupling โ€” MFEs had to know about each other's state shape, and a bad action in one MFE could corrupt another's state.

Option B โ€” Event Bus + Local State (what we switched to)

We built a lightweight event bus on window.__AK_BUS__ for cross-MFE communication. Each MFE owns its own local state and only publishes/subscribes to events it cares about:

// Shared event bus (host initialises this)
window.__AK_BUS__ = {
  emit: (event, payload) => window.dispatchEvent(
    new CustomEvent(event, { detail: payload })
  ),
  on: (event, cb) => window.addEventListener(event, e => cb(e.detail)),
  off: (event, cb) => window.removeEventListener(event, cb),
}

// In a remote MFE:
window.__AK_BUS__.emit('user:authenticated', { userId: '123' });

// In another remote:
window.__AK_BUS__.on('user:authenticated', ({ userId }) => {
  loadUserDashboard(userId);
});

Deployment Independence

The whole point of MFEs is independent deployment. Here's how we achieved it:

โš ๏ธ Don't skip the version contractDefine a strict contract (TypeScript interfaces) between host and remotes. Without it, a remote team refactoring their exported component props will silently break the host in production.

Team Ownership Model

TeamOwnsDeploys independently
Platform (us)Host shell, shared libs, event busWeekly
Tax Wizard teamtax-wizard MFEDaily
Dashboard teamdashboard MFEDaily
Profile teamuser-profile MFEOn demand

Results After Migration

Found this useful?

Share it with your frontend team ๐Ÿ‘‡