Felipe Moacir

Micro Frontends: Arquitetura Escalável

ArchitectureMicro FrontendsWebpack
Micro Frontends: Arquitetura Escalável

Micro Frontends aplicam o conceito de microservices no frontend: cada time desenvolve, deploya e mantém uma parte do app independentemente.

O Problema dos Monolitos Frontend

App grande:

  • 500k+ linhas de código
  • 20+ devs
  • Deploy demora 30min
  • Um bug quebra todo o app

Micro Frontends: Independência

Divida o app por domínios de negócio:

E-commerce:
  - Header (Team A)
  - Product Catalog (Team B)
  - Cart (Team C)
  - Checkout (Team D)

Cada time:

  • ✅ Usa framework próprio (React, Vue, Svelte)
  • ✅ Deploya independentemente
  • ✅ Tem pipeline CI/CD isolado

Module Federation (Webpack 5)

A solução mais popular. Permite compartilhar código em runtime:

Host App

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

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'host',
      remotes: {
        cart: 'cart@https://cart.example.com/remoteEntry.js',
        catalog: 'catalog@https://catalog.example.com/remoteEntry.js',
      },
      shared: {
        react: { singleton: true },
        'react-dom': { singleton: true },
      },
    }),
  ],
};

Uso

// App.jsx (Host)
import React, { lazy, Suspense } from 'react';

const Cart = lazy(() => import('cart/Cart'));
const Catalog = lazy(() => import('catalog/ProductList'));

export default function App() {
  return (
    <div>
      <h1>E-commerce</h1>
      
      <Suspense fallback={<div>Loading catalog...</div>}>
        <Catalog />
      </Suspense>
      
      <Suspense fallback={<div>Loading cart...</div>}>
        <Cart />
      </Suspense>
    </div>
  );
}

Remote App (Cart)

// webpack.config.js (Cart)
new ModuleFederationPlugin({
  name: 'cart',
  filename: 'remoteEntry.js',
  exposes: {
    './Cart': './src/Cart',
  },
  shared: {
    react: { singleton: true },
    'react-dom': { singleton: true },
  },
});

O host carrega o cart em runtime, sem rebuild.

Single-SPA: Framework Agnostic

Permite combinar React, Vue, Angular no mesmo app:

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

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

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

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

start();

Cada app monta/desmonta baseado na rota.

Comunicação Entre Microfrontends

Opção 1: Event Bus

// event-bus.js
const eventBus = new EventTarget();

export const publish = (event, data) => {
  eventBus.dispatchEvent(new CustomEvent(event, { detail: data }));
};

export const subscribe = (event, callback) => {
  eventBus.addEventListener(event, (e) => callback(e.detail));
};

// Cart.jsx
import { publish } from './event-bus';

function addToCart(product) {
  publish('cart:item-added', product);
}

// Header.jsx
import { subscribe } from './event-bus';

subscribe('cart:item-added', (product) => {
  updateCartCount();
});

Opção 2: Shared State (Context API)

// shared-context.js
import { createContext } from 'react';

export const CartContext = createContext();

// Host
<CartContext.Provider value={{ items, addItem }}>
  <Cart />
  <Checkout />
</CartContext.Provider>

Opção 3: URL/Query Params

/products?cartCount=3

Simples, mas limitado.

Quando Usar Micro Frontends?

Múltiplos times (5+ teams)
✅ App gigante (500k+ LOC)
✅ Times querem frameworks diferentes
✅ Deploy independente é crítico

❌ Time pequeno (menos de 10 devs)
❌ App simples
❌ Uniformidade é importante

Trade-offs

Vantagens

✅ Autonomia de times
✅ Deploys independentes
✅ Tech stack flexível
✅ Escala organizacionalmente

Desvantagens

❌ Complexidade operacional (CI/CD, monitoring)
❌ Bundle duplicado (React carregado múltiplas vezes?)
❌ Consistência de UI difícil
❌ Performance overhead (network requests)

Performance: Otimizações

Estratégia 1: Shared Dependencies

shared: {
  react: { singleton: true, eager: true }, // Carrega só 1x
}

Estratégia 2: Preload

<link rel="preload" href="https://cart.example.com/remoteEntry.js" as="script">

Estratégia 3: CDN + Cache

Hospede remotes em CDN com cache longo:

https://cdn.example.com/cart/v1.2.3/remoteEntry.js

Alternativas Modernas

Turbo + Monorepo

Não é micro frontend, mas resolve múltiplos times:

apps/
  web/          (Next.js)
  admin/        (Next.js)
packages/
  ui/           (Shared components)
  database/     (Prisma)
{
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev --parallel"
  }
}

Times trabalham independentemente, mas código é unificado.


Conclusão: Micro Frontends resolvem problemas organizacionais, não técnicos. Se seu time é pequeno, use monorepo. Se tem mais de 10 times, considere micro frontends.