As frontend applications grow in complexity, monolithic architectures often become bottlenecks for teams needing independent release cycles, technological flexibility, and scalable collaboration. Micro frontends—the practice of decomposing frontends into independently deployable units—offer solutions to these challenges. In this comprehensive guide, Andrew Maksimchenko (lead full-stack developer and solutions architect) shares hard-won insights from implementing micro frontends across large-scale projects.

The Case for Decomposition

Micro frontends extend microservice principles to the UI layer:

  • Team Autonomy: Teams own specific features (e.g., Search Team: React, Checkout Team: Vue)
  • Independent Deployments: Update product listings weekly while deploying CMS pages daily
  • Technology Agnosticism: Mix React, Angular, and vanilla JS seamlessly
  • Incremental Modernization: Replace legacy code section-by-section

"Think of it like Lego blocks—each self-contained, pluggable, and replaceable without breaking the whole system," Maksimchenko explains.

Method #1: Iframes & Cross-Window Messaging

The Original Micro-Frontend
Despite their reputation, iframes provide strong isolation:

// Parent app communication
iframe.contentWindow.postMessage({ type: 'init', userId: 42 }, '*');

// Child app (iframe) response
window.addEventListener('message', (event) => {
  if (event.data?.type === 'searchResult') {
    console.log('Results:', event.data.payload);
  }
});

Pros:
- Ironclad sandboxing
- Zero dependency conflicts
- Ideal for legacy/third-party integrations

Cons:
- Styling inconsistencies
- Complex communication
- Performance overhead

Use Cases: Payment gateways, embedded analytics, ads.

Method #2: Web Components (Custom Elements + Shadow DOM)

Native Browser Micro-Frontends
Create framework-agnostic UI elements:

class ProductTile extends HTMLElement {
  static observedAttributes = ['title'];

  constructor() {
    super();
    this.attachShadow({ mode: 'open' }).innerHTML = `
      <div id="title"></div>
    `;
  }

  attributeChangedCallback(name, _, newValue) {
    if (name === 'title') {
      this.shadowRoot.getElementById('title').textContent = newValue;
    }
  }
}
customElements.define('product-tile', ProductTile);

Pros:
- No framework lock-in
- Native browser support
- Style encapsulation via Shadow DOM

Cons:
- Limited state sharing
- Polyfills needed for IE
- Steep integration curve

Use Cases: Design systems, cross-framework widgets, incremental modernization.

Method #3: single-spa – The Meta-Framework

Orchestrating Multiple SPAs
Coordinate React/Vue/Angular apps under one router:

// Root config
import { registerApplication, start } from 'single-spa';

registerApplication({
  name: '@shop/products',
  app: () => System.import('@shop/products'),
  activeWhen: ['/products']
});

start();

Pros:
- Built-in routing/lifecycles
- Framework interoperability
- Lazy loading

Cons:
- Configuration complexity
- No built-in state management
- Client-side rendering only

Use Cases: Enterprise portals, multi-team SPAs, phased migrations.

Method #4: Module Federation (Webpack 5+)

Runtime Code Sharing
Dynamically load components across independently built apps:

// Remote app config (exposes component)
new ModuleFederationPlugin({
  name: 'productApp',
  filename: 'remoteEntry.js',
  exposes: { './ProductTile': './src/ProductTile.jsx' },
  shared: { react: { singleton: true } }
});

// Host app usage
const ProductTile = React.lazy(() => import('productApp/ProductTile'));

Pros:
- True runtime integration
- Shared dependency optimization
- No build-time coupling

Cons:
- Webpack dependency
- Version mismatch risks
- Complex error handling

Use Cases: Shared component libraries, dynamic plugin systems, composite applications.

Beyond the Big Four: Ecosystem Tools

  • Piral: Framework for portal-based micro frontends
  • Luigi: Enterprise-grade shells with RBAC
  • Bit: Component-driven development platform
  • Import Maps: Browser-native module resolution

Strategic Considerations

Choose your approach based on priorities:

Method Isolation DevEx Performance Complexity
Iframes ★★★★★ ★★☆ ★★☆ Low
Web Comp ★★★★☆ ★★★☆ ★★★★☆ Medium
single-spa ★★☆☆☆ ★★★☆ ★★★★☆ High
Module Fed ★★☆☆☆ ★★★★☆ ★★★★☆ Very High

"There's no universal solution," Maksimchenko concludes. "Iframes excel in security-critical contexts, Web Components deliver framework freedom, single-spa orchestrates complex SPAs, and Module Federation enables unprecedented runtime integration. Your choice must align with team structure, deployment needs, and long-term evolution strategy."

For teams navigating the micro-frontend landscape, this progression—from isolated iframes to dynamically federated modules—offers a roadmap toward scalable, resilient frontend architectures that grow with organizational complexity.

Source: Andrew Maksimchenko on freeCodeCamp