Micro Frontends divida para conquistar
_Como transformar seu frontend gigante em aplicações independentes, escaláveis e mantidas por times autônomos_
Micro Frontends divida para conquistar

Como transformar seu frontend gigante em aplicações independentes, escaláveis e mantidas por times autônomos
A dor que ninguém conta nos tutoriais
Imagine a seguinte cena: segunda-feira, 9h da manhã. Daily do time de frontend.
Dev 1: “Não vou conseguir subir meu PR hoje porque o João ainda não mergeou o dele e vai dar conflito gigante no App.tsx.”
Dev 2: “Eu tô travado porque preciso de uma feature do módulo de pagamento, mas o time responsável tá em outra sprint.”
Dev 3: “Fiz uma refatoração no sistema de rotas e quebrei três features de outros times. Vou ter que reverter tudo.”
Tech Lead: “Pessoal, vamos parar de deployar por hoje. O build tá demorando 23 minutos e não sabemos qual commit quebrou a aplicação.”
Essa não é ficção. É o dia a dia de milhares de times que construíram um monolito frontend.
Enquanto o backend há anos se dividiu em microsserviços, o frontend continuou sendo tratado como um bloco único, indivisível e cada vez maior. É como tentar fazer uma festa para 500 pessoas num apartamento de 50m² — tecnicamente possível, mas absolutamente insustentável.
E aí surge a pergunta que muda tudo: e se cada time pudesse ter seu próprio frontend?
A origem: quando o Spotify disse “chega de dependência”
Em 2016, os engenheiros do Spotify enfrentavam um problema clássico de escala: centenas de desenvolvedores trabalhando no mesmo repositório frontend. Cada deploy era um evento traumático. Cada refatoração, uma negociação política entre times.
A solução que eles encontraram foi revolucionária na sua simplicidade: dividir o frontend em pedaços independentes, cada um pertencendo a um time específico.
Não foi o Spotify que inventou o conceito — empresas como Ikea, Zalando e ThoughtWorks já experimentavam ideias similares — mas foram eles que popularizaram o termo Micro Frontend e mostraram que era possível aplicar os mesmos princípios dos microsserviços no frontend.
A promessa era simples e poderosa:
“Cada time desenvolve, testa e deploya seu pedaço do frontend de forma completamente independente.”
O que é Micro Frontend na prática?
Micro Frontend é uma arquitetura onde você divide sua aplicação frontend em pequenos pedaços independentes, cada um:
- Desenvolvido e mantido por um time específico
- Com seu próprio repositório e pipeline de deploy
- Usando potencialmente tecnologias diferentes
- Integrado em runtime numa aplicação maior
É como transformar aquele apartamento de 50m² em um prédio com apartamentos independentes. Cada morador (time) tem sua porta, suas chaves, sua autonomia. Mas todos fazem parte do mesmo condomínio (aplicação).
A diferença entre modularização e Micro Frontend
Isso NÃO é Micro Frontend:
meu-app/
├── src/
│ ├── modules/
│ │ ├── products/ # Pasta de produtos
│ │ ├── checkout/ # Pasta de checkout
│ │ └── profile/ # Pasta de perfil
│ └── App.tsx # Tudo compilado junto
Isso é apenas organização de pastas. Tudo ainda compila junto, deploya junto, quebra junto.
Isso SIM é Micro Frontend:
# Repositórios separados
products-mfe/ # Time A - Deploy independente
checkout-mfe/ # Time B - Deploy independente
profile-mfe/ # Time C - Deploy independente
shell-app/ # Container que orquestra tudo
Cada micro frontend é uma aplicação completa que vive sua própria vida.
Os tipos de Micro Frontend
Existem três abordagens principais para implementar micro frontends, cada uma com seus trade-offs:
- Build-Time Integration (Compile Time)
Micro frontends são integrados durante o build através de dependências npm.
// package.json do shell
{
"dependencies": {
"@mycompany/mfe-products": "^2.1.0",
"@mycompany/mfe-checkout": "^1.5.3",
"@mycompany/mfe-profile": "^3.0.1"
}
}
// App.tsx
import { ProductsApp } from "@mycompany/mfe-products";
import { CheckoutApp } from "@mycompany/mfe-checkout";
import { ProfileApp } from "@mycompany/mfe-profile";
export function App() {
return (
<Router>
<Route path="/products/*" element={<ProductsApp />} />
<Route path="/checkout/*" element={<CheckoutApp />} />
<Route path="/profile/*" element={<ProfileApp />} />
</Router>
);
}
Vantagens:
- Simples de implementar
- Não precisa infra complexa
- Type safety completo (TypeScript)
- Bundling otimizado
Desvantagens:
- Deploy acoplado (precisa rebuildar tudo)
- Não é independente de verdade
- Versões fixas no build
Quando usar: Times pequenos que querem modularização sem complexidade de runtime.
- Server-Side Integration (SSR/SSI)
Micro frontends são compostos no servidor antes de chegar no browser.
# nginx.conf
location /products {
proxy_pass http://products-mfe:3001;
}
location /checkout {
proxy_pass http://checkout-mfe:3002;
}
location /profile {
proxy_pass http://profile-mfe:3003;
}
Cada rota aponta para um servidor diferente servindo HTML completo.
Vantagens:
- SEO excelente
- First paint rápido
- Separação física real
Desvantagens:
- Difícil compartilhar estado
- Navegação entre MFEs recarrega a página
- Complexidade de infra
Quando usar: E-commerce, blogs, sites onde SEO é crítico.
- Runtime Integration (Client-Side)
Micro frontends são carregados dinamicamente no browser em runtime. Essa é a abordagem mais poderosa e complexa.
// shell/src/App.tsx
import { lazy, Suspense } from "react";
const ProductsApp = lazy(() => import("products_mfe/App"));
const CheckoutApp = lazy(() => import("checkout_mfe/App"));
const ProfileApp = lazy(() => import("profile_mfe/App"));
export function App() {
return (
<Router>
<Suspense fallback={<Loading />}>
<Route path="/products/*" element={<ProductsApp />} />
<Route path="/checkout/*" element={<CheckoutApp />} />
<Route path="/profile/*" element={<ProfileApp />} />
</Suspense>
</Router>
);
}
Esse import(‘products_mfe/App’) não vem do npm — vem de um servidor remoto em runtime.
Vantagens:
- Deploy 100% independente
- Pode usar versões diferentes de libs
- Atualização instantânea sem rebuild
- Verdadeira autonomia
Desvantagens:
- Complexidade técnica alta
- Possível duplicação de código
- Type safety mais difícil
Quando usar: Aplicações grandes com múltiplos times independentes.
Implementação prática: Module Federation
A forma mais moderna e poderosa de implementar runtime integration é usando Module Federation (Webpack 5 / Rspack / Vite).
Setup do Micro Frontend (Products)
// products-mfe/webpack.config.js
const ModuleFederationPlugin =
require("webpack").container.ModuleFederationPlugin;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: "products_mfe",
filename: "remoteEntry.js",
exposes: {
"./App": "./src/App",
"./ProductList": "./src/components/ProductList",
"./ProductDetail": "./src/components/ProductDetail",
},
shared: {
react: { singleton: true, requiredVersion: "^18.0.0" },
"react-dom": { singleton: true, requiredVersion: "^18.0.0" },
"react-router-dom": { singleton: true },
},
}),
],
};
O que está acontecendo aqui:
- exposes: componentes que esse MFE disponibiliza para outros
- shared: dependências compartilhadas entre MFEs (evita duplicação)
- singleton: garante que só existe UMA versão do React na página
Setup do Shell (Orquestrador)
// shell/webpack.config.js
const ModuleFederationPlugin =
require("webpack").container.ModuleFederationPlugin;
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: "shell",
remotes: {
products_mfe: "products_mfe@http://localhost:3001/remoteEntry.js",
checkout_mfe: "checkout_mfe@http://localhost:3002/remoteEntry.js",
profile_mfe: "profile_mfe@http://localhost:3003/remoteEntry.js",
},
shared: {
react: { singleton: true, requiredVersion: "^18.0.0" },
"react-dom": { singleton: true, requiredVersion: "^18.0.0" },
"react-router-dom": { singleton: true },
},
}),
],
};
Consumindo o Micro Frontend
// shell/src/App.tsx
import React, { Suspense, lazy } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
// Importação dinâmica de MFEs remotos
const ProductsApp = lazy(() => import("products_mfe/App"));
const CheckoutApp = lazy(() => import("checkout_mfe/App"));
const ProfileApp = lazy(() => import("profile_mfe/App"));
export function App() {
return (
<BrowserRouter>
<header>
<nav>
<a href="/products">Produtos</a>
<a href="/checkout">Carrinho</a>
<a href="/profile">Perfil</a>
</nav>
</header>
<main>
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/products/*" element={<ProductsApp />} />
<Route path="/checkout/*" element={<CheckoutApp />} />
<Route path="/profile/*" element={<ProfileApp />} />
</Routes>
</Suspense>
</main>
</BrowserRouter>
);
}
Magia acontecendo:
- Shell não tem o código de
ProductsAppem build time - Quando usuário acessa /products, o browser baixa o código dinamicamente de http://localhost:3001
- React renderiza o componente como se fosse local
- Se o time de produtos fizer deploy, a próxima request já pega a versão nova — sem rebuild do shell
Design Patterns essenciais
- Shell Application Pattern
O shell é a aplicação container responsável por:
// shell/src/App.tsx
import { ErrorBoundary } from "./components/ErrorBoundary";
import { AuthProvider } from "./contexts/AuthContext";
import { ThemeProvider } from "./contexts/ThemeContext";
export function App() {
return (
<ErrorBoundary>
<AuthProvider>
<ThemeProvider>
<Layout>
<Navigation />
<Suspense fallback={<Loading />}>
<MicroFrontendRoutes />
</Suspense>
</Layout>
</ThemeProvider>
</AuthProvider>
</ErrorBoundary>
);
}
Responsabilidades do shell:
- Roteamento principal
- Autenticação e autorização
- Temas e estilos globais
- Navigation/Header/Footer compartilhados
- Error boundaries
- Analytics e monitoramento
O que o shell NÃO deve fazer:
- Lógica de negócio
- Chamadas de API específicas
- Componentes complexos de domínio
- Shared State Pattern
Como compartilhar estado entre micro frontends independentes?
Opção 1: Context Provider no Shell
// shell/src/contexts/CartContext.tsx
export const CartContext = createContext<CartContextType | null>(null);
export function CartProvider({ children }: { children: ReactNode }) {
const [items, setItems] = useState<CartItem[]>([]);
const addItem = (item: CartItem) => {
setItems((prev) => [...prev, item]);
};
const removeItem = (id: string) => {
setItems((prev) => prev.filter((item) => item.id !== id));
};
return (
<CartContext.Provider value={{ items, addItem, removeItem }}>
{children}
</CartContext.Provider>
);
}
// products-mfe/src/components/ProductCard.tsx
import { useContext } from "react";
import { CartContext } from "shell/contexts/CartContext";
export function ProductCard({ product }: Props) {
// MFE consome contexto do shell
const cart = useContext(CartContext);
const handleAddToCart = () => {
cart?.addItem({
id: product.id,
name: product.name,
price: product.price,
});
};
return (
<div>
<h3>{product.name}</h3>
<button onClick={handleAddToCart}>Adicionar ao Carrinho</button>
</div>
);
}
Opção 2: Event Bus (Pub/Sub) — A que eu mais gosto, porem a mais perigosa
Quando você quer desacoplamento total entre MFEs:
// shell/src/utils/eventBus.ts
type EventCallback = (payload: any) => void;
class EventBus {
private events: Map<string, EventCallback[]> = new Map();
subscribe(event: string, callback: EventCallback) {
const handlers = this.events.get(event) || [];
this.events.set(event, [...handlers, callback]);
// Retorna função de cleanup
return () => {
const handlers = this.events.get(event) || [];
this.events.set(
event,
handlers.filter((h) => h !== callback)
);
};
}
publish(event: string, payload?: any) {
const handlers = this.events.get(event) || [];
handlers.forEach((handler) => handler(payload));
}
}
export const eventBus = new EventBus();
// Expõe globalmente para todos os MFEs
(window as any).__EVENT_BUS__ = eventBus;
// products-mfe: publica evento
import { eventBus } from "shell/utils/eventBus";
function ProductCard({ product }: Props) {
const handleAddToCart = () => {
eventBus.publish("cart:item-added", {
id: product.id,
name: product.name,
price: product.price,
});
};
return <button onClick={handleAddToCart}>Adicionar</button>;
}
// checkout-mfe: escuta evento
import { useEffect, useState } from "react";
import { eventBus } from "shell/utils/eventBus";
export function CartCounter() {
const [count, setCount] = useState(0);
useEffect(() => {
const unsubscribe = eventBus.subscribe("cart:item-added", () => {
setCount((prev) => prev + 1);
});
return unsubscribe;
}, []);
return <span>Carrinho ({count})</span>;
}
Vantagem: MFEs não conhecem uns aos outros. Totalmente desacoplados.
Desvantagem: Mais difícil debugar. Sem type safety. Se não tiver uma boa gestão dos eventos, quando começa a escalar pode virar uma bagunça.
Opção 3: State Management Global (Zustand/Redux)
// shell/src/store/cartStore.ts
import create from "zustand";
interface CartStore {
items: CartItem[];
addItem: (item: CartItem) => void;
removeItem: (id: string) => void;
}
export const useCartStore = create<CartStore>((set) => ({
items: [],
addItem: (item) =>
set((state) => ({
items: [...state.items, item],
})),
removeItem: (id) =>
set((state) => ({
items: state.items.filter((i) => i.id !== id),
})),
}));
// Expõe para todos os MFEs
(window as any).__CART_STORE__ = useCartStore;
// Qualquer MFE pode usar
const { items, addItem } = (window as any).__CART_STORE__();
- Styling Isolation Pattern
Como evitar conflito de estilos entre MFEs?
Não faça isso:
/* products-mfe */
.button {
background: blue;
}
/* checkout-mfe */
.button {
background: red; /* ❌ Conflito! */
}
Opção 1: CSS Modules
// products-mfe/ProductCard.module.css
.button {
background: blue;
}
// ProductCard.tsx
import styles from "./ProductCard.module.css";
export function ProductCard() {
return <button className={styles.button}>Comprar</button>;
// Gera: <button class="ProductCard_button__a1b2c">
}
Opção 2: CSS-in-JS com prefixo
// products-mfe
import styled from "@emotion/styled";
const Button = styled.button`
background: blue;
// CSS escopado automaticamente
`;
Opção 3: Shadow DOM
// Isola completamente o CSS
class ProductWidget extends HTMLElement {
connectedCallback() {
const shadow = this.attachShadow({ mode: "open" });
shadow.innerHTML = `
<style>
.button { background: blue; }
</style>
<button class="button">Comprar</button>
`;
}
}
customElements.define("product-widget", ProductWidget);
4. Versioning Strategy Pattern
Como gerenciar versões de MFEs?
// shell/webpack.config.js - Ambiente de dev
remotes: {
products_mfe: 'products_mfe@http://localhost:3001/remoteEntry.js',
}
// shell/webpack.config.js - Produção
remotes: {
products_mfe: 'products_mfe@https://cdn.myapp.com/products-mfe/v2.3.1/remoteEntry.js',
}
Estratégia de versionamento:
// Configuração dinâmica em runtime
const MFE_REGISTRY = {
products: {
url: process.env.REACT_APP_PRODUCTS_MFE_URL,
version: "2.3.1",
fallback: "/fallback-products",
},
checkout: {
url: process.env.REACT_APP_CHECKOUT_MFE_URL,
version: "1.5.0",
fallback: "/fallback-checkout",
},
};
function loadMicroFrontend(name: string) {
const config = MFE_REGISTRY[name];
return import(/* webpackIgnore: true */ config.url).catch(() => {
console.error(`Failed to load ${name}, using fallback`);
return import(config.fallback);
});
}
Quando usar Micro Frontends
Sinais de que você PRECISA de micro frontends:
- Múltiplos times trabalhando no mesmo frontend
Se você tem mais de 2–3 times mexendo no mesmo repositório, os conflitos de merge e dependências começam a atrapalhar mais do que ajudar.
- Aplicação com domínios bem separados
E-commerce:
├── Catálogo de produtos (Time A)
├── Carrinho e Checkout (Time B)
├── Perfil e pedidos (Time C)
└── Admin/Backoffice (Time D)
Cada domínio é quase uma aplicação separada. Faz sentido separá-los.
- Deploy frequente e independente
Se o time de produtos precisa deployar 5x por dia, mas o time de checkout só deploya 1x por semana, eles não deveriam estar no mesmo build.
- Tecnologia heterogênea
Legado em AngularJS + novo em React? Micro frontends permitem migração gradual:
// Shell pode carregar ambos
<Route path="/legacy/*" element={<AngularMFE />} />
<Route path="/new/*" element={<ReactMFE />} />
- Escala de organização
Empresas com 50+ desenvolvedores frontend geralmente se beneficiam da autonomia que micro frontends trazem.
Checklist: você está pronto?
- [ ] Tem pelo menos 3 times independentes trabalhando no frontend
- [ ] Domínios de negócio bem definidos e separados
- [ ] Necessidade de deploy independente
- [ ] Infraestrutura para servir múltiplas aplicações
- [ ] Time com conhecimento técnico em arquitetura distribuída
Quando NÃO usar Micro Frontends
Sinais de que você vai se arrepender:
- Aplicação pequena ou time pequeno
Se você tem 2–5 desenvolvedores, a complexidade de micro frontends vai atrapalhar mais do que ajudar.
Não faça isso:
startup-app/
├── products-mfe/ # 1 dev
├── checkout-mfe/ # 1 dev
├── profile-mfe/ # 1 dev
└── shell/ # Overhead absurdo
- Domínios muito acoplados
Se suas “features” conversam o tempo todo e compartilham muita lógica, separar vai criar mais problemas:
// Má separação
user-profile-mfe → precisa de dados de orders-mfe
orders-mfe → precisa de dados de products-mfe
products-mfe → precisa de dados de user-profile-mfe
// Isso não é separação, é bagunça distribuída
- Performance é crítica
Micro frontends adicionam overhead:
- Múltiplos bundles sendo baixados
- Possível duplicação de dependências
- Runtime de orquestração
Se você está construindo um dashboard de trading em tempo real onde cada milissegundo importa, talvez um monolito bem otimizado seja melhor.
- Falta de maturidade DevOps
Micro frontends exigem:
- CI/CD separado para cada MFE
- CDN ou servidores para hospedar cada bundle
- Monitoramento distribuído
- Estratégia de versionamento
Se sua infra ainda é “FTP para produção”, não está pronto.
- Aplicação com UX muito integrada
Se sua aplicação é tipo Figma, Notion ou VSCode — onde tudo é uma experiência fluida e integrada — micro frontends podem quebrar essa coesão.
Red flags de má implementação:
// MFE comunicando diretamente com outro MFE
import { getUserData } from 'profile-mfe/utils';
// Lógica de negócio no shell
function Shell() {
const products = await fetchProducts(); // ❌ não!
return <ProductsMFE products={products} />;
}
// Dependências desalinhadas
products-mfe: React 17
checkout-mfe: React 18
profile-mfe: React 16
// = caos de singleton
// Cada MFE com seu próprio design system
// Parece Frankenstein na UI
A combinação perfeita: Micro Frontends + BFF
Lembra do artigo sobre BFF? Micro Frontends e BFF são feitos um para o outro.
Arquitetura completa
┌─────────────────────────────────────────────┐
│ SHELL APP │
│ (routing, auth, layout compartilhado) │
└─────────────────────────────────────────────┘
│ │ │
┌────▼───┐ ┌────▼───┐ ┌────▼───┐
│Products│ │Checkout│ │Profile │
│ MFE │ │ MFE │ │ MFE │
└────┬───┘ └────┬───┘ └────┬───┘
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│Products │ │Checkout │ │Profile │
│ BFF │ │ BFF │ │ BFF │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
┌────▼─────────────▼──────────────▼─────┐
│ MICROSSERVIÇOS │
│ (orders, users, products, etc) │
└───────────────────────────────────────┘
Implementação prática
// products-mfe/src/services/api.ts
const PRODUCTS_BFF_URL = process.env.REACT_APP_PRODUCTS_BFF;
export async function getProducts(filters: ProductFilters) {
// Chama o BFF específico deste MFE
const response = await fetch(`${PRODUCTS_BFF_URL}/products`, {
method: "POST",
body: JSON.stringify(filters),
});
return response.json();
}
// products-bff/src/routes/products.ts
app.post("/products", async (req, res) => {
// BFF agrega dados de múltiplos microsserviços
const [products, inventory, reviews] = await Promise.all([
productsService.search(req.body.filters),
inventoryService.getStock(productIds),
reviewsService.getRatings(productIds),
]);
// Retorna exatamente o que o products-mfe precisa
res.json({
products: products.map((p) => ({
id: p.id,
name: p.name,
price: p.price,
stock: inventory[p.id].quantity,
rating: reviews[p.id].average,
// Dados otimizados para este MFE
})),
});
});
Benefícios da combinação
Autonomia total:
- Time possui MFE + BFF + testes + deploy
- Não depende de outros times para evoluir
- Pode escolher tecnologias específicas
Performance otimizada:
- MFE carrega apenas seu código
- BFF retorna apenas dados necessários
- Sem over-fetching ou under-fetching
Contratos claros:
// products-mfe/types/api.ts
export interface ProductListResponse {
products: Array<{
id: string;
name: string;
price: number;
stock: number;
rating: number;
}>;
total: number;
page: number;
}
BFF garante esse contrato. MFE confia no contrato. Backend pode mudar internamente sem quebrar nada. Caso queria saber mais sobre BFF, recomendo fortemente ler o artigo:
BFF: Por que o Frontend deveria liderar essa revolução?
Armadilhas comuns (e como evitar)
- “Micro” frontends gigantes
// Isso não é micro
products-mfe/
├── src/
│ ├── catalog/
│ ├── cart/
│ ├── checkout/
│ ├── orders/
│ ├── reviews/
│ └── recommendations/
Se seu “micro” frontend faz 6 coisas diferentes, não é micro.
Corrija:
catalog-mfe/ → Só catálogo
cart-mfe/ → Só carrinho
checkout-mfe/ → Só checkout
orders-mfe/ → Só pedidos
- Duplicação excessiva de código
// Cada MFE reimplementa o mesmo botão
products - mfe / src / Button.tsx;
checkout - mfe / src / Button.tsx;
profile - mfe / src / Button.tsx;
Corrija: Crie uma lib compartilhada
// @mycompany/design-system (npm package)
export { Button, Input, Modal } from './components';
// products-mfe/package.json
{
"dependencies": {
"@mycompany/design-system": "^2.1.0"
}
}
Configure como shared no Module Federation:
shared: {
'@mycompany/design-system': {
singleton: true,
requiredVersion: '^2.0.0',
},
}
- Comunicação caótica entre MFEs
// products-mfe chamando checkout-mfe diretamente
import { addToCart } from "checkout-mfe/actions";
addToCart(product); // Acoplamento!
Corrija: Use event bus ou shared context
// Desacoplado via eventos
eventBus.publish("product:add-to-cart", { productId: product.id });
- Falta de error handling
// Se o MFE falhar, quebra tudo
<Suspense fallback={<Loading />}>
<ProductsMFE />
</Suspense>
Corrija: Error boundaries + fallback
// Degrada gracefully
<ErrorBoundary
fallback={<ErrorFallback message="Produtos indisponíveis" />}
onError={(error) => logToMonitoring(error)}
>
<Suspense fallback={<Loading />}>
<ProductsMFE />
</Suspense>
</ErrorBoundary>
- Dependências desalinhadas
// Versões diferentes de React
products-mfe: react@18.2.0
checkout-mfe: react@18.0.0
profile-mfe: react@17.0.2
Resultado: três cópias do React no bundle. Pesadelo de performance.
// Todos os MFEs devem ter:
shared: {
react: {
singleton: true,
requiredVersion: '^18.2.0',
strictVersion: true, // Falha se versão incompatível
},
}
Melhores práticas consolidadas
- Defina contratos claros
// shared-types/src/contracts.ts
export interface MicroFrontendContract {
name: string;
version: string;
exposes: string[];
requires: {
react: string;
"react-router-dom": string;
};
events: {
emits: string[];
listens: string[];
};
}
// products-mfe/contract.ts
export const contract: MicroFrontendContract = {
name: "products-mfe",
version: "2.1.0",
exposes: ["./App", "./ProductList"],
requires: {
react: "^18.2.0",
"react-router-dom": "^6.0.0",
},
events: {
emits: ["product:added-to-cart", "product:viewed"],
listens: ["cart:cleared"],
},
};
- Teste contratos (Contract Testing)
// products-mfe/tests/contract.test.ts
import { Pact } from "@pact-foundation/pact";
describe("Products MFE Contract", () => {
it("should provide ProductList component", async () => {
const { ProductList } = await import("products-mfe/ProductList");
expect(ProductList).toBeDefined();
expect(typeof ProductList).toBe("function");
});
it("should emit correct events", () => {
const eventSpy = jest.fn();
eventBus.subscribe("product:added-to-cart", eventSpy);
// Simula ação
addProductToCart("123");
expect(eventSpy).toHaveBeenCalledWith({
productId: "123",
timestamp: expect.any(Number),
});
});
});
- Monitore cada MFE separadamente
// shell/src/monitoring.ts
import * as Sentry from "@sentry/react";
export function setupMFEMonitoring(mfeName: string) {
Sentry.init({
dsn: process.env.REACT_APP_SENTRY_DSN,
environment: process.env.NODE_ENV,
tags: {
microFrontend: mfeName,
},
});
}
// products-mfe/src/index.tsx
import { setupMFEMonitoring } from "shell/monitoring";
setupMFEMonitoring("products-mfe");
// Agora erros são taggeados por MFE
- Versionamento semântico rigoroso
MAJOR.MINOR.PATCH
MAJOR: Breaking changes (muda contrato)
MINOR: Novas features (mantém compatibilidade)
PATCH: Bug fixes
// products-mfe v2.0.0 → v3.0.0 (breaking change)
// ANTES:
export function ProductList() { ... }
// DEPOIS:
export function ProductList({ config }: { config: Config }) { ... }
// Quebra quem usa
// Shell precisa atualizar:
- <ProductList />
+ <ProductList config={defaultConfig} />
- Feature flags por MFE
// feature-flags.ts
export const FEATURES = {
"products-mfe": {
newProductCard: true,
aiRecommendations: false,
},
"checkout-mfe": {
expressCheckout: true,
pixPayment: true,
},
};
// products-mfe/src/ProductList.tsx
import { FEATURES } from "shell/feature-flags";
export function ProductList() {
const showNewCard = FEATURES["products-mfe"].newProductCard;
return (
<>
{products.map((p) =>
showNewCard ? (
<NewProductCard product={p} />
) : (
<OldProductCard product={p} />
)
)}
</>
);
}
Como pensar em Micro Frontends
Micro frontends não são sobre tecnologia. São sobre organização e autonomia.
A pergunta que devemos fazer não é “como implementar Module Federation?”, mas sim:
”Como estruturamos os times para que possam evoluir independentemente?”
Quando você escreve um micro frontend pensando “isso precisa funcionar sem depender de outros times”, você está no caminho certo.
Quando você escreve pensando “vou só separar em pastas e chamar de micro frontend”, você está criando complexidade desnecessária.
Os princípios para levar
- Autonomia > Reuso → Prefira duplicar código a criar dependências entre MFEs (eu sei que isso pode ferir o DRY, mas as vezes é valido comprar um tradeoff se os beneficios forem maiores)
- Contratos > Implementação → Garanta compatibilidade através de contratos, não de código compartilhado
- Comunicação assíncrona → MFEs devem conversar via eventos, não via chamadas diretas
- Falhas isoladas → Se um MFE quebrar, os outros continuam funcionando
- Deploy independente → Se precisa coordenar deploy de múltiplos MFEs, algo está errado
Conclusão: divida para conquistar
A indústria de software aprendeu há anos que monólitos backend não escalam. Microsserviços revolucionaram o backend não porque são tecnicamente superiores, mas porque resolvem um problema de organização humana.
Micro frontends trazem a mesma revolução para o frontend.
O problema não é técnico. O problema é permitir que times cresçam sem se atropelarem.
A verdadeira habilidade não está em configurar webpack — está em estruturar times e código para que ambos possam evoluir.
A pergunta final
Antes de implementar micro frontends, pergunte-se:
”Meus times realmente precisam de autonomia total, ou só estou seguindo a moda?”
Se a resposta for “precisamos de autonomia”, micro frontends são a solução.
Se a resposta for “só queremos modularizar”, crie uma arquitetura de pastas melhor.
Micro frontends não são sobre separar código. São sobre separar responsabilidades. E código bem organizado não é aquele que compila — é aquele que permite times trabalharem sem se bloquearem.
Referências e aprofundamento
Este artigo foi construído com base em práticas consolidadas e implementações reais:
- Cam Jackson — Micro Frontends (martinfowler.com): artigo seminal que popularizou o conceito.
- Luca Mezzalira — Building Micro-Frontends: livro definitivo sobre arquitetura de micro frontends.
- Michael Geers — Micro Frontends in Action: guia prático com exemplos reais.
- Webpack Module Federation — Documentação oficial: a tecnologia que viabilizou runtime integration moderna.
- Single-SPA — Framework para micro frontends: alternativa ao Module Federation, agnóstica de framework.
- Zack Jackson — Criador do Module Federation: palestras e artigos sobre a arquitetura.
- ThoughtWorks Technology Radar — Avaliações e recomendações sobre adoção de micro frontends.
Se este artigo te fez repensar como estruturar seu frontend, ele cumpriu seu papel. Compartilhe com seu time e vamos elevar o nível da arquitetura frontend brasileira.
👏 Gostou? Deixe um clap e compartilhe suas experiências com micro frontends nos comentários!
메타데이터
- post_id
- 77d59ff2bdcb
- slug
- micro-frontends-divida-para-conquistar-77d59ff2bdcb
- url
- https://medium.com/@davysz/micro-frontends-divida-para-conquistar-77d59ff2bdcb
- canonical_url
- https://medium.com/@davysz/micro-frontends-divida-para-conquistar-77d59ff2bdcb
- author_url
- https://medium.com/@davysz
- status
- ok
- fetched_at
- 2026-07-13 17:43:34