micro-frontends

star 10

Micro-frontend architecture — composition approaches, Module Federation, single-spa, web components, shared state, and inter-app communication. Covers the patterns and tradeoffs for splitting a frontend across independent teams. USE FOR: micro-frontend architecture, Module Federation, single-spa, runtime composition, independent frontend deployment, cross-team frontend development DO NOT USE FOR: single-team SPA development (use spa), server-side rendering (use ssr), backend service decomposition (use dev/architecture/microservices)

Tyler-R-Kendrick By Tyler-R-Kendrick schedule Updated 2/11/2026

name: micro-frontends description: | Micro-frontend architecture — composition approaches, Module Federation, single-spa, web components, shared state, and inter-app communication. Covers the patterns and tradeoffs for splitting a frontend across independent teams. USE FOR: micro-frontend architecture, Module Federation, single-spa, runtime composition, independent frontend deployment, cross-team frontend development DO NOT USE FOR: single-team SPA development (use spa), server-side rendering (use ssr), backend service decomposition (use dev/architecture/microservices) license: MIT metadata: displayName: "Micro-Frontend Architecture" author: "Tyler-R-Kendrick" compatibility: claude, copilot, cursor references:


Micro-Frontend Architecture

Overview

Micro-frontends extend the microservices idea to the frontend: independently developed, tested, and deployed frontend applications that are composed into a single, cohesive user experience. Each team owns a vertical slice of the product — from the UI layer down through the API — with autonomy over their tech stack, build pipeline, and release cadence. The approach trades simplicity for team scalability.

Composition Approaches

┌──────────────────────────────────────────────────────────────────┐
│                  Micro-Frontend Composition                       │
│                                                                   │
│  Build-Time             Runtime                  Server-Side      │
│  ┌──────────────┐       ┌──────────────────┐     ┌────────────┐  │
│  │ npm packages │       │ Module Federation│     │ ESI / SSI  │  │
│  │ Monorepo     │       │ Import Maps      │     │ SSR Comp.  │  │
│  │ imports      │       │ iframes          │     │ Tailor /   │  │
│  │              │       │ Web Components   │     │ Podium     │  │
│  └──────────────┘       └──────────────────┘     └────────────┘  │
│                                                                   │
│  + Type safety            + Independent deploy    + SEO-friendly  │
│  + Optimized bundle       + Tech-stack freedom    + Fast FCP      │
│  - Coupled releases       - Runtime overhead      - Complexity    │
│  - Single deploy          - Larger bundles        - Server infra  │
└──────────────────────────────────────────────────────────────────┘

Build-Time Composition

Each micro-frontend is published as an npm package and consumed by a host application at build time.

// package.json of the host/shell application
{
  "dependencies": {
    "@org/header": "^2.1.0",
    "@org/product-catalog": "^3.4.0",
    "@org/checkout": "^1.8.0",
    "@org/user-profile": "^2.0.0"
  }
}

Tradeoff: Strong type safety and optimized bundles, but all micro-frontends must be built and deployed together — defeating much of the independence benefit.

Runtime Composition — Module Federation

Webpack 5 Module Federation (and Rspack) allows applications to share code at runtime. A "host" application dynamically loads "remote" modules from independently deployed bundles.

┌──────────────────────────────────────────────────────────┐
│  Host (Shell Application)                                 │
│                                                           │
│  Loads at runtime:                                        │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐      │
│  │ Remote A     │  │ Remote B     │  │ Remote C     │      │
│  │ (Product)    │  │ (Cart)       │  │ (Profile)    │      │
│  │ React 18     │  │ Vue 3        │  │ React 18     │      │
│  │ Port 3001    │  │ Port 3002    │  │ Port 3003    │      │
│  └─────────────┘  └─────────────┘  └─────────────┘      │
│                                                           │
│  Shared: react, react-dom (singleton)                     │
└──────────────────────────────────────────────────────────┘

Runtime Composition — Import Maps

Native browser import maps allow mapping bare module specifiers to URLs, enabling runtime loading without a bundler federation plugin.

<script type="importmap">
{
  "imports": {
    "@org/header": "https://cdn.example.com/header/v2.1.0/index.js",
    "@org/product": "https://cdn.example.com/product/v3.4.0/index.js",
    "react": "https://cdn.example.com/react/18.2.0/react.production.min.js"
  }
}
</script>

Runtime Composition — iframes

The simplest (and oldest) isolation approach. Each micro-frontend runs in its own iframe with full CSS and JS isolation. Drawbacks: no shared DOM, awkward communication, SEO-invisible, accessibility challenges.

Server-Side Composition

Fragments are assembled on the server before sending HTML to the client. Approaches include Edge Side Includes (ESI), Server-Side Includes (SSI), and frameworks like Podium (Node.js) or Piral.

Module Federation (Webpack 5 / Rspack)

Remote Configuration

// webpack.config.js — Remote (Product Team)
const { ModuleFederationPlugin } = require('webpack').container;

module.exports = {
  output: {
    publicPath: 'https://product.example.com/',
    uniqueName: 'product',
  },
  plugins: [
    new ModuleFederationPlugin({
      name: 'product',
      filename: 'remoteEntry.js',
      exposes: {
        './ProductList': './src/components/ProductList',
        './ProductDetail': './src/components/ProductDetail',
        './productApi': './src/api/products',
      },
      shared: {
        react: { singleton: true, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true, requiredVersion: '^18.0.0' },
        '@tanstack/react-query': { singleton: true },
      },
    }),
  ],
};

Host Configuration

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

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'shell',
      remotes: {
        product: 'product@https://product.example.com/remoteEntry.js',
        cart: 'cart@https://cart.example.com/remoteEntry.js',
        profile: 'profile@https://profile.example.com/remoteEntry.js',
      },
      shared: {
        react: { singleton: true, eager: true, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true, eager: true, requiredVersion: '^18.0.0' },
        '@tanstack/react-query': { singleton: true },
      },
    }),
  ],
};

Dynamic Remotes

// Load remotes dynamically based on configuration
async function loadRemoteModule(scope: string, module: string) {
  // Initialize sharing scope
  await __webpack_init_sharing__('default');

  const container = window[scope];
  await container.init(__webpack_share_scopes__.default);

  const factory = await container.get(module);
  return factory();
}

// Usage — load a remote component at runtime
const ProductList = React.lazy(() =>
  loadRemoteModule('product', './ProductList')
);

function App() {
  return (
    <Suspense fallback={<Skeleton />}>
      <ProductList />
    </Suspense>
  );
}

single-spa

single-spa is a framework-agnostic micro-frontend orchestrator. It manages the lifecycle of multiple frontend applications (called "applications" or "parcels") within a single page.

Core Concepts

  • Applications: Full page-level micro-frontends mounted/unmounted based on URL routes
  • Parcels: Framework-agnostic components that can be mounted anywhere, regardless of route
  • Layout Engine: Declarative HTML-based routing for micro-frontends

Root Config

// root-config.js
import { registerApplication, start } from 'single-spa';

registerApplication({
  name: '@org/navbar',
  app: () => System.import('@org/navbar'),
  activeWhen: () => true, // always active
});

registerApplication({
  name: '@org/products',
  app: () => System.import('@org/products'),
  activeWhen: '/products',
  customProps: { authToken: getAuthToken() },
});

registerApplication({
  name: '@org/checkout',
  app: () => System.import('@org/checkout'),
  activeWhen: '/checkout',
});

start();

single-spa Application (React)

// @org/products — single-spa-react wrapper
import React from 'react';
import ReactDOMClient from 'react-dom/client';
import singleSpaReact from 'single-spa-react';
import App from './App';

const lifecycles = singleSpaReact({
  React,
  ReactDOMClient,
  rootComponent: App,
  errorBoundary(err, info, props) {
    return <div>Error loading Products module</div>;
  },
});

export const { bootstrap, mount, unmount } = lifecycles;

Web Components as Micro-Frontend Boundaries

Web Components provide native browser encapsulation (Shadow DOM) that makes them excellent micro-frontend boundaries — each team ships a custom element that encapsulates its framework and styles.

// Product team ships a custom element
class ProductCard extends HTMLElement {
  private root: ShadowRoot;

  constructor() {
    super();
    this.root = this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    const productId = this.getAttribute('product-id');
    // Mount React/Vue/Svelte inside the shadow DOM
    this.render(productId);
  }

  disconnectedCallback() {
    // Cleanup framework instance
  }

  static get observedAttributes() {
    return ['product-id'];
  }

  attributeChangedCallback(name: string, oldVal: string, newVal: string) {
    if (name === 'product-id') this.render(newVal);
  }

  private render(productId: string | null) {
    // Render framework component into shadow root
  }
}

customElements.define('product-card', ProductCard);

Usage in any framework or plain HTML:

<product-card product-id="abc-123"></product-card>

Shared State and Communication

Micro-frontends are independently deployed, so they cannot share in-memory state directly. Communication strategies:

Custom Events (Recommended)

// Publishing micro-frontend
window.dispatchEvent(
  new CustomEvent('cart:item-added', {
    detail: { productId: 'abc-123', quantity: 1 },
  })
);

// Consuming micro-frontend
window.addEventListener('cart:item-added', (event: CustomEvent) => {
  const { productId, quantity } = event.detail;
  updateCartBadge(productId, quantity);
});

Shared Observable (Event Bus)

// shared-state.ts — published as a shared package
import { BehaviorSubject } from 'rxjs';

export interface AppState {
  user: User | null;
  cartCount: number;
  theme: 'light' | 'dark';
}

export const appState$ = new BehaviorSubject<AppState>({
  user: null,
  cartCount: 0,
  theme: 'light',
});

// Any micro-frontend can subscribe
appState$.subscribe((state) => {
  console.log('Cart count:', state.cartCount);
});

// Any micro-frontend can update
appState$.next({ ...appState$.value, cartCount: 5 });

URL-Based Communication

Use URL search params or path segments as shared state. Each micro-frontend reads from and writes to the URL — the URL becomes the single source of truth.

// Product micro-frontend sets filter
const url = new URL(window.location.href);
url.searchParams.set('category', 'electronics');
url.searchParams.set('sort', 'price-asc');
window.history.pushState({}, '', url);

// Sidebar micro-frontend reads filter
const params = new URLSearchParams(window.location.search);
const category = params.get('category');
const sort = params.get('sort');

Tradeoffs

Dimension Benefit Cost
Team Autonomy Teams choose their own framework, tooling, release cadence Inconsistent UX without strong design system governance
Independent Deployment Ship features without coordinating with other teams Versioning complexity, integration testing burden
Tech-Stack Freedom Adopt new frameworks incrementally, avoid full rewrites Multiple frameworks = larger aggregate bundle, more to learn
Fault Isolation One micro-frontend crashing does not bring down the page Error boundaries and fallbacks add complexity
Bundle Size Each team optimizes their own bundle Duplicate dependencies if sharing is not configured correctly
Routing Each team owns their routes Cross-app navigation, deep linking, and shared layouts are harder
Shared Dependencies Module Federation singleton sharing reduces duplication Version mismatches can cause subtle runtime errors
Organizational Fit Scales with the number of teams (Conway's Law) Overkill for small teams — adds overhead without benefit

When to Use Micro-Frontends

Use when:

  • Multiple teams (3+) work on the same user-facing application
  • Teams need independent deployment and release cadences
  • You are migrating a large legacy frontend incrementally (strangler fig pattern)
  • Different parts of the app have fundamentally different requirements (e.g., a dashboard vs. a content editor)

Do not use when:

  • A single team owns the entire frontend
  • The application is small to medium in scope
  • You can solve the problem with a well-organized monorepo and code splitting
  • You lack the infrastructure for independent builds, deployments, and integration testing

Best Practices

  • Establish a shared design system (component library, tokens, CSS variables) — team autonomy does not mean visual chaos.
  • Use Module Federation's singleton: true for critical shared dependencies (React, router) to avoid duplicate instances.
  • Define clear contracts between micro-frontends — custom events with typed payloads, not ad-hoc DOM coupling.
  • Invest in integration testing — test the composed page, not just individual micro-frontends in isolation.
  • Start with a monolith and extract micro-frontends only when organizational pain (release coordination, merge conflicts, team coupling) justifies the architectural complexity.
  • Use a shell/host application that owns the layout, authentication, and top-level routing — micro-frontends own their content area only.
  • Monitor aggregate bundle size — without vigilance, micro-frontends will independently add dependencies until the total payload dwarfs a monolith.
Install via CLI
npx skills add https://github.com/Tyler-R-Kendrick/agent-skills --skill micro-frontends
Repository Details
star Stars 10
call_split Forks 3
navigation Branch main
article Path SKILL.md
More from Creator
Tyler-R-Kendrick
Tyler-R-Kendrick Explore all skills →