Back to blog

Microfrontends: The Architecture Enabling Independent Teams to Scale Giant Applications

Hello HaWkers, is your frontend application becoming an unmaintainable monolith, with dozens of developers stepping on each other's toes?

Microfrontends emerged as a solution to this problem. In 2025, more than 60% of enterprise companies adopted this architecture to scale development without losing velocity. Let's understand how it works and when it's worth it.

The Problem Microfrontends Solve

Imagine a large e-commerce: product catalog, shopping cart, checkout, user profile, admin area. Everything in a single giant repository.

Typical consequences:

  • Deploying a simple feature goes through review by 15 people
  • Build takes 20+ minutes
  • Daily merge conflicts
  • A bug in the cart can crash the catalog
  • Teams block each other

Microfrontends propose: Divide the frontend into independent applications, each developed, tested, and deployed by an autonomous team.

Module Federation: The Modern Solution

Module Federation (Webpack 5+) revolutionized microfrontends, allowing code sharing at runtime without duplication.

Basic Architecture

// host-app/webpack.config.js (Main shell)
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'host',
      remotes: {
        catalog: 'catalog@http://localhost:3001/remoteEntry.js',
        cart: 'cart@http://localhost:3002/remoteEntry.js',
        checkout: 'checkout@http://localhost:3003/remoteEntry.js'
      },
      shared: {
        react: { singleton: true, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true, requiredVersion: '^18.0.0' }
      }
    })
  ]
};

// catalog-app/webpack.config.js (Catalog microfrontend)
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'catalog',
      filename: 'remoteEntry.js',
      exposes: {
        './ProductList': './src/components/ProductList',
        './ProductDetails': './src/components/ProductDetails'
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true }
      }
    })
  ]
};

Consuming Microfrontends

// Main app consuming microfrontends
import React, { lazy, Suspense } from 'react';

// Dynamic import of microfrontends
const ProductList = lazy(() => import('catalog/ProductList'));
const ShoppingCart = lazy(() => import('cart/ShoppingCart'));
const CheckoutFlow = lazy(() => import('checkout/CheckoutFlow'));

function App() {
  return (
    <div className="app">
      <nav>{/* Shared navigation */}</nav>

      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/products" element={<ProductList />} />
          <Route path="/cart" element={<ShoppingCart />} />
          <Route path="/checkout" element={<CheckoutFlow />} />
        </Routes>
      </Suspense>
    </div>
  );
}

microfrontends architecture

Practical Implementation: Communication Between Microfrontends

Shared Event Bus

// shared/eventBus.ts
type EventCallback = (data: any) => void;

class EventBus {
  private events: Map<string, EventCallback[]> = new Map();

  subscribe(event: string, callback: EventCallback) {
    if (!this.events.has(event)) {
      this.events.set(event, []);
    }
    this.events.get(event)!.push(callback);
  }

  publish(event: string, data: any) {
    const callbacks = this.events.get(event) || [];
    callbacks.forEach(callback => callback(data));
  }

  unsubscribe(event: string, callback: EventCallback) {
    const callbacks = this.events.get(event) || [];
    this.events.set(event, callbacks.filter(cb => cb !== callback));
  }
}

export const eventBus = new EventBus();

// catalog-app/ProductDetails.tsx
import { eventBus } from '@shared/eventBus';

function addToCart(product: Product) {
  eventBus.publish('cart:add', {
    productId: product.id,
    name: product.name,
    price: product.price
  });
}

// cart-app/ShoppingCart.tsx
import { eventBus } from '@shared/eventBus';
import { useEffect, useState } from 'react';

function ShoppingCart() {
  const [items, setItems] = useState<CartItem[]>([]);

  useEffect(() => {
    const handleAddToCart = (data: any) => {
      setItems(prev => [...prev, data]);
    };

    eventBus.subscribe('cart:add', handleAddToCart);

    return () => {
      eventBus.unsubscribe('cart:add', handleAddToCart);
    };
  }, []);

  return (
    <div>
      <h2>Cart: {items.length} items</h2>
      {items.map(item => (
        <CartItem key={item.productId} item={item} />
      ))}
    </div>
  );
}

Real Benefits in Production

Spotify: Autonomous Teams

Spotify uses microfrontends to allow 100+ squads to develop independently. Each squad has complete domain over its part of the application.

Ikea: Independent Deploy

Ikea reduced deployment time from 4 hours to 15 minutes. Each microfrontend deploys when ready, without global coordination.

Zalando: Technical Scalability

Zalando allows teams to choose different technologies (React, Vue, Svelte) in the same product, as needed.

Challenges and Pitfalls

1. Performance Overhead

Multiple bundles can increase total payload by 15%.

Solution: Use Module Federation to share dependencies, implement aggressive code splitting.

2. UI Consistency

Each team can create different visual components.

Solution: Shared design system, base components as federated library.

3. Complex Routing

Navigation between microfrontends can be confusing.

Solution: Host shell manages main routing, microfrontends manage internal routes.

4. Difficult Debugging

Error can be in any microfrontend.

Solution: Centralized logging, complete stack traces, observability tools.

5. Versioning

Incompatibility between versions of shared dependencies.

Solution: Rigorous semantic versioning, integration tests between microfrontends.

When NOT to Use Microfrontends

Be honest about your needs:

Don't use if:

  • Team has fewer than 10 developers
  • Application is small/medium (< 50 components)
  • You don't have coordination problems between teams
  • Infrastructure is limited

Microfrontends are OVERKILL for most projects. The added complexity is only justified at enterprise scale.

Simpler Alternatives

Before microfrontends, consider:

  1. Well-structured monorepo (Nx/Turborepo)
  2. Feature flags for independent development
  3. Component library shared
  4. Modular architecture within the monolith

The Future: Native Micro Frontends

WebComponents and native ES Modules promise to drastically simplify microfrontends, eliminating the need for complex tooling.

Microfrontends are not a silver bullet. They are a solution for a specific problem: scaling development with large, independent teams. Honestly assess if this is your real problem.

If you want to understand how to manage code at even larger scale, see: Map: Transform Data in JavaScript where we explore fundamental patterns.

Let's go! 🦅

🎯 Join Developers Who Are Evolving

Thousands of developers already use our material to accelerate their studies and achieve better positions in the market.

Why invest in structured knowledge?

Learning in an organized way with practical examples makes all the difference in your journey as a developer.

Start now:

  • 3x of R$34.54 on credit card
  • or R$97.90 upfront

🚀 Access Complete Guide

"Excellent material for those who want to go deeper!" - João, Developer

Comments (0)

This article has no comments yet 😢. Be the first! 🚀🦅

Add comments