Micro Frontends · How it works

2 min read
Senior4 min read
Rapid overview

How it works

Core Concepts

What Are Micro Frontends?

  • Independent Deployment: Each micro frontend can be deployed independently
  • Technology Agnostic: Teams can choose different frameworks (React, Angular, Vue, etc.)
  • Team Ownership: Each team owns a specific business domain end-to-end
  • Isolated Development: Teams work in isolation without coordination overhead
  • Incremental Upgrades: Update parts of the application without rewriting everything

When to Use Micro Frontends

Good Use Cases:

  • Large applications with multiple teams
  • Applications that need technology diversity
  • Long-lived applications requiring gradual modernization
  • Organizations with independent business domains
  • Teams that need to scale independently

Not Recommended For:

  • Small applications with a single team
  • Applications requiring tight integration
  • Performance-critical applications (overhead considerations)
  • Short-lived projects

Implementation Approaches

1. Build-Time Integration

Micro frontends are composed at build time using package management.

{
  "name": "shell-app",
  "dependencies": {
    "@company/header-mfe": "^1.2.0",
    "@company/product-mfe": "^2.1.0",
    "@company/checkout-mfe": "^1.0.5"
  }
}

Pros:

  • Simple to implement
  • Good performance (single bundle)
  • Type safety across boundaries

Cons:

  • All micro frontends must be deployed together
  • Difficult to have independent deployments
  • Versioning can be complex

2. Runtime Integration via iframes

Each micro frontend runs in its own iframe.

<div class="app-container">
  <iframe src="https://header.example.com" />
  <iframe src="https://products.example.com" />
  <iframe src="https://footer.example.com" />
</div>

Pros:

  • Complete isolation (CSS, JS, global state)
  • Easy to implement
  • Technology agnostic

Cons:

  • Performance overhead
  • Difficult routing and deep linking
  • Complex communication between frames
  • Poor UX (scrolling, responsiveness)
  • SEO challenges

3. Runtime Integration via JavaScript

Micro frontends are loaded dynamically at runtime.

// Shell application
const loadMicroFrontend = async (name, host) => {
  const script = document.createElement('script');
  script.src = `${host}/remoteEntry.js`;
  script.onload = () => {
    window[name].init({
      sharedDependencies: {
        react: React,
        'react-dom': ReactDOM
      }
    });
  };
  document.head.appendChild(script);
};

await loadMicroFrontend('headerMFE', 'https://header.example.com');
await loadMicroFrontend('productMFE', 'https://products.example.com');

Pros:

  • Independent deployments
  • Runtime composition
  • Shared dependencies

Cons:

  • More complex setup
  • Runtime overhead
  • Version management complexity

4. Web Components

Use web components as a standard for creating micro frontends.

// Product micro frontend
class ProductCatalog extends HTMLElement {
  connectedCallback() {
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.innerHTML = `
      <style>
        :host { display: block; }
        .product { padding: 1rem; }
      </style>
      <div class="product-catalog">
        <h2>Products</h2>
        <div id="products"></div>
      </div>
    `;
    this.loadProducts();
  }

  async loadProducts() {
    const products = await fetch('/api/products').then(r => r.json());
    this.renderProducts(products);
  }

  renderProducts(products) {
    const container = this.shadowRoot.querySelector('#products');
    container.innerHTML = products.map(p => `
      <div class="product">${p.name} - $${p.price}</div>
    `).join('');
  }
}

customElements.define('product-catalog', ProductCatalog);

Usage:

<product-catalog></product-catalog>

Pros:

  • Standards-based
  • Great encapsulation
  • Framework agnostic
  • Native browser support

Cons:

  • Learning curve
  • Limited framework integration
  • SSR challenges

5. Module Federation (Webpack 5+)

Share code and dependencies across micro frontends at runtime.

// webpack.config.js for Header MFE
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'header',
      filename: 'remoteEntry.js',
      exposes: {
        './Header': './src/Header'
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true }
      }
    })
  ]
};

// webpack.config.js for Shell App
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'shell',
      remotes: {
        header: 'header@https://header.example.com/remoteEntry.js',
        products: 'products@https://products.example.com/remoteEntry.js'
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true }
      }
    })
  ]
};

// Usage in Shell App
import React, { lazy, Suspense } from 'react';

const Header = lazy(() => import('header/Header'));
const Products = lazy(() => import('products/ProductList'));

function App() {
  return (
    <div>
      <Suspense fallback={<div>Loading header...</div>}>
        <Header />
      </Suspense>
      <Suspense fallback={<div>Loading products...</div>}>
        <Products />
      </Suspense>
    </div>
  );
}

Pros:

  • True runtime integration
  • Shared dependencies
  • Independent deployments
  • Built into Webpack 5

Cons:

  • Webpack-specific
  • Complex configuration
  • Debugging can be difficult

Real-World Examples

  • Spotify: Uses micro frontends for different sections
  • Zalando: Implements micro frontends for their e-commerce platform
  • IKEA: Uses micro frontends for product catalogs
  • SoundCloud: Implements different features as micro frontends

See also