Micro-frontends existem porque monolitos frontend ficam lentos para desenvolver quando múltiplos times trabalham no mesmo código. Mas a solução introduz complexidade própria, e a maioria dos times que adota micro-frontends subestima essa complexidade.

O problema que micro-frontends resolvem

Em times grandes, 5+ squads trabalhando no mesmo repositório frontend enfrentam: conflitos de merge constantes, builds que ficam mais lentos a cada sprint, difficulty de deploy independente por squad, e acoplamento entre features que deveriam ser independentes.

Micro-frontends separam a aplicação em unidades independentes que são compostas na runtime. Cada squad controla sua parte, faz deploy independente, e não bloqueia os outros.

Abordagens que existem

Build-time integration (Module Federation)

Webpack Module Federation permite que aplicações carreguem módulos de outras aplicações em runtime. Cada micro-frontend é uma aplicação standalone que exporta componentes.

// webpack.config.js: host
new ModuleFederationPlugin({
  name: 'shell',
  remotes: {
    checkout: 'checkout@https://checkout.example.com/remoteEntry.js',
    catalog: 'catalog@https://catalog.example.com/remoteEntry.js',
  },
})

Run-time integration (iframe ou Web Components)

iframes são a abordagem mais isolada. Web Components permitem composição mais granular, mas exigem uma camada de comunicação entre micro-frontends.

Server-side composition (SSI, ESI, edge-side includes)

O servidor compõe micro-frontends antes de enviar ao cliente. Elimina waterfalls de carregamento mas requer infraestrutura de servidor que suporte includes.

O que muda na prática

// Estrutura típica
shell-app/              # Shell que monta a página
├── src/
│   ├── Header.tsx      # Compartilhado entre todos
│   ├── Footer.tsx      # Compartilhado entre todos
│   └── MicroApp.tsx    # Loader de micro-frontends
│
checkout/               # Squad de checkout
├── src/
│   ├── Checkout.tsx
│   ├── Payment.tsx
│   └── Shipping.tsx
│
catalog/                # Squad de catálogo
├── src/
│   ├── ProductList.tsx
│   └── ProductDetail.tsx

Os custos que ninguém avisa

  • Comunicação entre micro-frontends: dados compartilhados exigem um mecanismo explícito, event bus, store compartilhada, ou prop drilling via shell.
  • Estilo consistente: design system compartilhado é obrigatório, não opcional. Cada squad pode customizar, mas a base vem de um pacote comum.
  • Performance: múltiplos bundles significam mais requests HTTP, mais JavaScript parseado, mais memória. O shell precisa otimizar carregamento.
  • Debug: um bug que cruza fronteiras entre micro-frontends é mais difícil de rastrear do que num monolito.
  • Deploy orchestrado: dependências entre micro-frontends podem exigir deploy coordenado, o que elimina parte da independência.

Quando vale a pena

Micro-frontends se justificam quando: você tem 3+ times trabalhando no mesmo frontend, com releases independentes e pouca sobreposição de código. O ganho de velocity do time supera o custo de infraestrutura.

Quando não vale a pena

Se você tem 1-2 times, ou os squads trabalham em features que se sobrepõem constantemente, monolito com boa organização de pastas e módulos é mais simples e mais eficiente. Não adote arquitetura por antecipar problemas que você não tem.