Voltar para o Blog
Anúncio

Microfrontends: A Arquitetura que Grandes Empresas Usam para Escalar Times e Aplicações

Olá HaWkers, imagine ter 50 desenvolvedores trabalhando na mesma aplicação frontend sem que um time bloqueie o outro. Parece impossível? Empresas como Spotify, IKEA, Amazon e Zalando fazem isso todos os dias usando microfrontends - e essa arquitetura está se tornando cada vez mais relevante em 2025.

O problema que microfrontends resolve é muito real: à medida que aplicações e times crescem, monólitos de frontend se tornam gargalos. Deploy coordenado entre times, conflitos de merge intermináveis, tecnologias legadas travando inovação. Microfrontends trazem para o frontend os mesmos benefícios que microserviços trouxeram para o backend.

O Que São Microfrontends?

Microfrontends é uma arquitetura que divide uma aplicação frontend em pedaços menores e independentes, cada um podendo ser desenvolvido, testado e deployado por times diferentes usando potencialmente tecnologias diferentes.

Pense em um e-commerce: o time de catálogo cuida da listagem de produtos, o time de checkout cuida do carrinho e pagamento, o time de conta cuida do perfil do usuário. Cada time trabalha em seu próprio "micro-app", mas para o usuário final tudo parece uma aplicação única e integrada.

A grande sacada é autonomia. Cada time pode escolher sua stack (React, Vue, Angular), definir seu próprio ciclo de release, e fazer deploys independentes sem precisar coordenar com outros 10 times.

// Exemplo conceitual: Estrutura de um projeto com microfrontends
/*
meu-ecommerce/
├── container/              // Shell application que orquestra tudo
│   ├── src/
│   │   ├── App.jsx
│   │   └── bootstrap.jsx
│   ├── webpack.config.js   // Module Federation config
│   └── package.json
├── products/               // Microfrontend de produtos (time A)
│   ├── src/
│   │   ├── ProductList.jsx
│   │   ├── ProductDetail.jsx
│   │   └── index.js       // Exports para expor
│   ├── webpack.config.js
│   └── package.json       // React 18 + suas libs
├── cart/                   // Microfrontend de carrinho (time B)
│   ├── src/
│   │   ├── Cart.jsx
│   │   ├── Checkout.jsx
│   │   └── index.js
│   ├── webpack.config.js
│   └── package.json       // Pode até usar Vue se quiser!
└── account/                // Microfrontend de conta (time C)
    ├── src/
    │   ├── Profile.jsx
    │   ├── Orders.jsx
    │   └── index.js
    ├── webpack.config.js
    └── package.json       // React 17 + libs específicas
*/

// container/webpack.config.js - Orquestrador
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'container',
      remotes: {
        // Carrega microfrontends remotamente
        products: 'products@http://localhost:3001/remoteEntry.js',
        cart: 'cart@http://localhost:3002/remoteEntry.js',
        account: 'account@http://localhost:3003/remoteEntry.js'
      },
      shared: {
        // Compartilha dependências para evitar duplicação
        react: { singleton: true, requiredVersion: '^18.0.0' },
        'react-dom': { singleton: true, requiredVersion: '^18.0.0' }
      }
    })
  ]
};

// container/src/App.jsx - Composição dos microfrontends
import React, { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

// Lazy load dos microfrontends
const ProductList = lazy(() => import('products/ProductList'));
const ProductDetail = lazy(() => import('products/ProductDetail'));
const Cart = lazy(() => import('cart/Cart'));
const Checkout = lazy(() => import('cart/Checkout'));
const Profile = lazy(() => import('account/Profile'));
const Orders = lazy(() => import('account/Orders'));

function App() {
  return (
    <BrowserRouter>
      <div className="app">
        <Header /> {/* Componente do container */}

        <Suspense fallback={<div>Carregando...</div>}>
          <Routes>
            {/* Rotas gerenciadas por produtos */}
            <Route path="/products" element={<ProductList />} />
            <Route path="/products/:id" element={<ProductDetail />} />

            {/* Rotas gerenciadas por cart */}
            <Route path="/cart" element={<Cart />} />
            <Route path="/checkout" element={<Checkout />} />

            {/* Rotas gerenciadas por account */}
            <Route path="/profile" element={<Profile />} />
            <Route path="/orders" element={<Orders />} />
          </Routes>
        </Suspense>

        <Footer /> {/* Componente do container */}
      </div>
    </BrowserRouter>
  );
}

export default App;

Cada microfrontend roda em seu próprio servidor de desenvolvimento e pode ser deployado independentemente. O container os orquestra em runtime.

Anúncio

Module Federation: A Tecnologia que Tornou Microfrontends Práticos

Module Federation, introduzido no Webpack 5, revolucionou microfrontends ao permitir compartilhamento dinâmico de código JavaScript entre aplicações completamente separadas.

Antes do Module Federation, implementar microfrontends era complexo e cheio de trade-offs. Agora é surpreendentemente elegante.

// products/webpack.config.js - Microfrontend que expõe componentes
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin');
const deps = require('./package.json').dependencies;

module.exports = {
  entry: './src/index.js',
  mode: 'development',
  devServer: {
    port: 3001,
    headers: {
      'Access-Control-Allow-Origin': '*'
    }
  },

  plugins: [
    new ModuleFederationPlugin({
      name: 'products',
      filename: 'remoteEntry.js',

      // O que este microfrontend expõe
      exposes: {
        './ProductList': './src/components/ProductList',
        './ProductDetail': './src/components/ProductDetail',
        './ProductCard': './src/components/ProductCard'
      },

      // O que pode consumir de outros
      remotes: {
        cart: 'cart@http://localhost:3002/remoteEntry.js'
      },

      // Dependências compartilhadas
      shared: {
        ...deps,
        react: {
          singleton: true,
          requiredVersion: deps.react,
          eager: false
        },
        'react-dom': {
          singleton: true,
          requiredVersion: deps['react-dom'],
          eager: false
        }
      }
    })
  ]
};

// products/src/components/ProductList.jsx
import React, { useState, useEffect } from 'react';
// Pode importar componente de OUTRO microfrontend!
import { AddToCartButton } from 'cart/AddToCartButton';

export default function ProductList() {
  const [products, setProducts] = useState([]);

  useEffect(() => {
    fetch('/api/products')
      .then(res => res.json())
      .then(setProducts);
  }, []);

  return (
    <div className="product-grid">
      {products.map(product => (
        <div key={product.id} className="product-card">
          <img src={product.image} alt={product.name} />
          <h3>{product.name}</h3>
          <p className="price">${product.price}</p>

          {/* Usando componente do microfrontend "cart" */}
          <AddToCartButton productId={product.id} />
        </div>
      ))}
    </div>
  );
}

A mágica está em como Module Federation gerencia dependências compartilhadas. Se products e cart usam React 18, apenas uma cópia é carregada e compartilhada entre eles.

microfrontends architecture

Anúncio

Padrões de Comunicação Entre Microfrontends

O desafio em microfrontends é comunicação. Como o microfrontend de carrinho sabe quando um produto foi adicionado? Como sincronizar estado entre apps independentes?

Existem vários padrões:

1. Custom Events (Browser API)

// products/ProductCard.jsx - Dispara evento
function addToCart(product) {
  // Lógica local
  const event = new CustomEvent('product:added-to-cart', {
    detail: { product }
  });
  window.dispatchEvent(event);
}

// cart/CartIcon.jsx - Escuta evento
import { useEffect, useState } from 'react';

function CartIcon() {
  const [itemCount, setItemCount] = useState(0);

  useEffect(() => {
    function handleProductAdded(event) {
      console.log('Produto adicionado:', event.detail.product);
      setItemCount(prev => prev + 1);
    }

    window.addEventListener('product:added-to-cart', handleProductAdded);

    return () => {
      window.removeEventListener('product:added-to-cart', handleProductAdded);
    };
  }, []);

  return (
    <div className="cart-icon">
      🛒 {itemCount > 0 && <span className="badge">{itemCount}</span>}
    </div>
  );
}

2. Shared State Store (Redux, Zustand, etc)

// shared/store.js - Store compartilhado por todos
import create from 'zustand';

export const useCartStore = create((set) => ({
  items: [],
  addItem: (product) => set((state) => ({
    items: [...state.items, product]
  })),
  removeItem: (productId) => set((state) => ({
    items: state.items.filter(item => item.id !== productId)
  })),
  getTotal: () => {
    // Lógica de cálculo
  }
}));

// Exposto pelo container e compartilhado
// container/webpack.config.js
new ModuleFederationPlugin({
  name: 'container',
  exposes: {
    './store': './src/shared/store'
  },
  // ...
});

// products/ProductCard.jsx - Usa store compartilhado
import { useCartStore } from 'container/store';

function ProductCard({ product }) {
  const addItem = useCartStore(state => state.addItem);

  return (
    <div>
      <h3>{product.name}</h3>
      <button onClick={() => addItem(product)}>
        Adicionar ao Carrinho
      </button>
    </div>
  );
}

// cart/Cart.jsx - Também usa o mesmo store
import { useCartStore } from 'container/store';

function Cart() {
  const items = useCartStore(state => state.items);
  const removeItem = useCartStore(state => state.removeItem);

  return (
    <div>
      <h2>Carrinho ({items.length})</h2>
      {items.map(item => (
        <div key={item.id}>
          <span>{item.name}</span>
          <button onClick={() => removeItem(item.id)}>Remover</button>
        </div>
      ))}
    </div>
  );
}

3. Props Drilling (Container passa props)

// container/App.jsx - Container gerencia estado e passa props
import React, { useState } from 'react';
import ProductList from 'products/ProductList';
import CartIcon from 'cart/CartIcon';

function App() {
  const [cartItems, setCartItems] = useState([]);

  const handleAddToCart = (product) => {
    setCartItems(prev => [...prev, product]);
  };

  const handleRemoveFromCart = (productId) => {
    setCartItems(prev => prev.filter(item => item.id !== productId));
  };

  return (
    <div>
      <header>
        <CartIcon
          items={cartItems}
          onRemove={handleRemoveFromCart}
        />
      </header>

      <ProductList onAddToCart={handleAddToCart} />
    </div>
  );
}
Anúncio

Desafios e Trade-offs dos Microfrontends

Microfrontends não são uma solução mágica. Eles trazem complexidade:

1. Duplicação de Dependências Mesmo com shared modules, diferentes versões de bibliotecas podem ser carregadas, aumentando o bundle total.

2. Complexidade de Deploy Agora você tem múltiplos deploys para coordenar. Versionamento e compatibilidade entre microfrontends requer disciplina.

3. Testes End-to-End Testar a integração completa é mais complexo quando partes rodam em repositórios e pipelines diferentes.

4. Performance Network requests adicionais para carregar remotes podem impactar tempo de carregamento inicial.

// Estratégia para minimizar problemas de performance
// container/webpack.config.js
new ModuleFederationPlugin({
  name: 'container',
  remotes: {
    // Em produção, usar CDN com versão específica
    products: process.env.NODE_ENV === 'production'
      ? 'products@https://cdn.example.com/products/v1.2.3/remoteEntry.js'
      : 'products@http://localhost:3001/remoteEntry.js',
  },
  shared: {
    react: {
      singleton: true,
      eager: true,  // Carrega imediatamente no container
      requiredVersion: '^18.0.0'
    }
  }
});

// Prefetch strategy para melhorar perceived performance
// container/src/App.jsx
import { useEffect } from 'react';

function App() {
  useEffect(() => {
    // Prefetch microfrontends que provavelmente serão usados
    import('products/ProductList');
    import('cart/CartIcon');
  }, []);

  return (
    // ...
  );
}

Quando Usar (E Quando NÃO Usar) Microfrontends

Use microfrontends quando:

  • Você tem múltiplos times trabalhando no mesmo produto
  • Partes da aplicação evoluem em ritmos muito diferentes
  • Você precisa migrar gradualmente de uma tecnologia para outra
  • Diferentes partes têm requisitos de escala muito diferentes

NÃO use microfrontends quando:

  • Seu time tem menos de 10 desenvolvedores
  • Sua aplicação é relativamente simples
  • Você não tem infraestrutura robusta de CI/CD
  • Performance é crítica e você não pode aceitar overhead

Para a maioria dos projetos pequenos e médios, um monólito bem estruturado é mais simples e eficaz.

O Futuro dos Microfrontends

A tendência é forte. Ferramentas como Single-SPA, Bit, e agora Webpack Module Federation tornaram microfrontends acessíveis. Em 2025, vemos crescimento na adoção, especialmente em empresas médias-grandes.

Frameworks estão se adaptando. Há soluções emergentes para Vite (vite-plugin-federation) e discussões sobre suporte nativo em Next.js e outros meta-frameworks.

A arquitetura continuará evoluindo, mas o conceito core de autonomia de times e deploys independentes veio para ficar.

Se você está intrigado por arquiteturas que permitem escala, recomendo dar uma olhada em outro artigo: React Server Components: A Nova Era do Desenvolvimento Full-Stack onde você vai descobrir outra abordagem revolucionária para estruturar aplicações modernas.

Bora pra cima! 🦅

🎯 Junte-se aos Desenvolvedores que Estão Evoluindo

Milhares de desenvolvedores já usam nosso material para acelerar seus estudos e conquistar melhores posições no mercado.

Por que investir em conhecimento estruturado?

Aprender de forma organizada e com exemplos práticos faz toda diferença na sua jornada como desenvolvedor.

Comece agora:

  • 3x de R$34,54 no cartão
  • ou R$97,90 à vista

🚀 Acessar Guia Completo

"Material excelente para quem quer se aprofundar!" - João, Desenvolvedor

Anúncio
Post anteriorPróximo post

Comentários (0)

Esse artigo ainda não possui comentários 😢. Seja o primeiro! 🚀🦅

Adicionar comentário