Architecture9 min read · Updated 2026-06-01

Microfrontends vs Monolith: When to Split Your Frontend

A practical guide to when microfrontend architecture makes sense, how Module Federation works, and the real trade-offs that interviewers want you to know.

💡 Practice these concepts interactively with AI feedback

Start Practicing →

Microfrontends vs Monolith: When to Split Your Frontend

The microfrontend hype cycle has passed. Now we can have an honest conversation about when they're worth the complexity — and when a monolith is simply better.

What Problem Do Microfrontends Actually Solve?

The answer is almost entirely organizational.

Microfrontends solve the team scaling bottleneck: when 20 developers all commit to the same frontend repo, merge conflicts are constant, CI takes 30 minutes, and a broken change by Team A blocks Team B's release.

Microfrontends don't primarily solve performance, DX, or code quality problems. If you reach for them for those reasons, you'll be disappointed.

The right question: "Do we have multiple teams that need independent deployment cadences?"

  • One team, one product → Monolith
  • 2-3 teams, shared repo okay → Monolith or modular monolith
  • 5+ teams, independent release cadence → Microfrontends worth considering

---

The Integration Approaches

### 1. Webpack Module Federation (Recommended for React shops)

Module Federation is built into Webpack 5. One app exposes components; another consumes them — downloaded at runtime from the other app's deployed URL.

// Cart Team (Remote) — webpack.config.js
new ModuleFederationPlugin({
  name: 'cart',
  filename: 'remoteEntry.js',
  exposes: {
    './CartWidget': './src/CartWidget',
  },
  shared: {
    react: { singleton: true, requiredVersion: '^18.0.0' },
    'react-dom': { singleton: true },
  },
})

// Shell App (Host) — webpack.config.js new ModuleFederationPlugin({ name: 'shell', remotes: { cart: 'cart@https://cart.company.com/remoteEntry.js', }, shared: { react: { singleton: true }, 'react-dom': { singleton: true }, }, })

// Shell usage — CartWidget loads from cart.company.com at runtime const CartWidget = React.lazy(() => import('cart/CartWidget'));

The key: singleton: true — this ensures both the shell and the cart remote use the same React instance. Without it, you'll have two copies of React and hooks will break.

### 2. Single-SPA

Framework-agnostic orchestrator. Registers multiple "micro-apps" and mounts/unmounts them based on the URL.

registerApplication({
  name: '@company/checkout',
  app: () => import('@company/checkout'),
  activeWhen: ['/checkout'],
});

Single-SPA is the right choice when teams use different frameworks (some React, some Vue, legacy Angular). Module Federation is better for same-framework teams.

### 3. iframes

Strongest isolation. Each microfrontend is completely sandboxed. Zero CSS/JS conflicts.

The problems: awful accessibility, fixed heights, no shared routing, terrible mobile experience. Use only for isolated embeds (a payment widget, an embedded analytics dashboard).

---

Cross-MFE Communication

This is where most teams get it wrong.

Wrong: Import from another MFE's package directly.

// This creates tight coupling — defeats the purpose import { useCart } from '@company/cart';

Right: Custom events or a shared event bus.

// Cart MFE fires an event window.dispatchEvent(new CustomEvent('cart:item-added', {   detail: { productId: '123', quantity: 1 } }));

// Header MFE listens window.addEventListener('cart:item-added', (e) => { setCartCount(prev => prev + e.detail.quantity); });

The shell (container app) owns global state — user session, auth tokens, feature flags. Micro-apps receive what they need as props at mount time and fire events to communicate back up.

---

The Real Trade-offs

### What you gain:

  • Independent deployment (deploy cart without touching checkout)
  • Team autonomy (Cart Team owns their stack)
  • Isolated failures (cart deploy broken? Checkout still works)

### What you pay:

  • Bundle duplication: even with shared config, you'll ship more bytes than a monolith
  • Design inconsistency: each team naturally drifts from the design system
  • Debugging complexity: errors cross team boundaries; distributed tracing required
  • Operational overhead: 5 separate CI pipelines, 5 Webpack configs, 5 deployment targets
  • Version compatibility: "Cart requires React 18.2, but shell is on 18.0" — real problem in practice

---

When the Monolith Wins

A well-structured modular monolith with clear module boundaries is easier to build, test, and debug than a microfrontend architecture for most team sizes.

Nx and Turborepo let you have monorepo benefits (shared code, unified CI) with module boundaries (enforced import rules between packages) without the runtime complexity of Module Federation.

Consider this path first: 1. Monorepo with pnpm workspaces 2. Nx with enforced module boundaries 3. Feature-based folder structure (each feature is an isolated directory) 4. Shared packages for UI components, hooks, utilities

Only add the runtime MFE layer (Module Federation) when independent deployment is a genuine business requirement.

---

Interview Answer Template

"I'd use microfrontends when we have 5+ teams with genuinely different release cadences and different areas of ownership. For the technical approach I'd use Webpack Module Federation because it allows runtime composition while sharing React instances. The key challenges I'd plan for are: shared state via events not direct imports, a unified design system owned by a platform team, and coordinated versioning of shared dependencies. For smaller organisations I'd first try a modular monolith in a Nx monorepo with enforced module boundaries — you get team boundaries without the operational complexity."

Put This Into Practice

Reading articles is passive. JSPrep Pro makes you actively recall, predict output, and get AI feedback.

Start Free →Browse All Questions

Related Articles

Deep Dive
We Built a RAG-Powered AI Question Engine Into a JavaScript Interview Platform — Here's Exactly How It Works
12 min read
Build Systems
Monorepo with Turborepo vs Nx: The Complete Comparison (2025)
9 min read
Core Concepts
map() vs forEach() in JavaScript: Which One to Use and Why It Matters
7 min read