blense.
InícioFrontendProgramaçãoIANotíciasGuiasCheatsheetsSobre

Tecnologia decodificada, sempre.

Uma leitura curada sobre IA, produto e a cultura por trás do código. Sem ruído, sem hype.

blense.

Tecnologia com foco. Histórias sobre o que a inovação realmente muda.

Seções

FrontendInteligência ArtificialProgramação & DevNotícias Tech

Blense

SobreRSSPolítica de privacidadeTermosCookies
© 2026 Blense · blense.fun
Programação & Dev

Microfrontends com Angular e Nx: o que ninguém conta

Todo tutorial ensina a configurar microfrontends em minutos, mas poucos revelam o caos arquitetural que surge após meses de deploy em produção.

Gpor Genildo Souza28 de jul.16 min de leitura
Microfrontends com Angular e Nx: o que ninguém conta
Desafios reais de Microfrontends com Angular
  • 1Microfrontends frequentemente levam três meses para implementar e nove meses para estabilizar devido a problemas de produção não previstos.
  • 2O compartilhamento de estado global entre remotes anula a independência dos deploys, tornando o sistema acoplado e difícil de manter.
  • 3Conflitos de CSS entre remotes são problemas não determinísticos que exigem o uso de namespaces rigorosos para evitar vazamento de estilos.
  • 4O uso do Nx com configurações estritas de versionamento é essencial para evitar falhas silenciosas causadas por incompatibilidades de bibliotecas.
  • 5Manter versões diferentes do Angular em um mesmo monorepo Nx é inviável, sendo necessário realizar migrações globais em todo o workspace.

Todo tutorial mostra como configurar o Module Federation em 20 minutos. Nenhum mostra o que acontece 6 meses depois em produção — o CSS que vaza entre remotes, a versão do Angular que derruba o shell, o estado compartilhado que tornou o deploy independente impossível, e o custo organizacional que o diagrama arquitetural não captura.


A realidade que o tutorial não mostra

O tutorial termina onde o problema começa.

A maioria das equipes que implanta microfrontends chega a produção em 3 meses e gasta os próximos 9 meses resolvendo problemas que nenhum tutorial mencionou.

O modelo é sedutor: times independentes, deploys independentes, autonomia de tecnologia. Na prática, a independência prometida tem um custo que só aparece quando um time tenta subir uma versão do Angular enquanto outro ainda está no ciclo anterior. Ou quando o CSS do remote de checkout começa a afetar o layout do shell. Ou quando você descobre que o estado compartilhado que colocou em um serviço global tornou o deploy independente tecnicamente impossível.

“A arquitetura de microfrontends não falha no código. Falha nas fronteiras que você não definiu.

Microfrontends com Angular e Nx: o que ninguém conta

→ O número que importa Em uma pesquisa com 20+ projetos de microfrontend de 2021-2023, o padrão era consistente: 3 meses para implementar, 9 meses para estabilizar. Os problemas de state compartilhado, CSS leaking e version mismatch não aparecem em demos — aparecem quando os times começam a trabalhar de forma genuinamente independente.

A arquitetura de microfrontends não falha no código. Falha nas fronteiras que você não definiu.


Os 5 problemas de produção

O que acontece 6 meses depois do deploy.

Arsenal do dev de elite.
Pare de arriscar seu frontend: a arquitetura definitiva do Angular 2218 minSolidJS em 2026: Por que a reatividade fina está superando o Virtual DOM28 minLazy Loading: carregar menos para entregar mais13 minTypeScript com Zustand + TanStack Query: os tipos que os tutoriais não mostram8 min

Problema 01 — Version mismatch — o crash silencioso: Time A usa Angular 21. Time B faz upgrade para Angular 22 com a nova API de Signal Forms. O shell carrega os dois. O Module Federation tenta reconciliar as versões com singleton: true. Às vezes funciona. Às vezes o remote B crasha sem mensagem de erro óbvia.

Problema 02 — CSS leaking — o estilo que não tem fronteiras: O remote de checkout define .btn { background: red } sem escopo. O shell tem .btn { background: blue }. Dependendo da ordem de carregamento, um sobrescreve o outro. Em produção, a ordem muda com caching. O bug é não determinístico.

Problema 03 — Estado global — o deploy que não é independente: Para compartilhar o usuário autenticado, alguém colocou o estado em um NgRx Store compartilhado entre todos os remotes. Agora, qualquer mudança no shape do estado exige deploy coordenado de todos os remotes. O deploy "independente" deixou de ser independente.

Problema 04 — DX em desenvolvimento — 4 terminais para 1 feature: Para rodar o feature de checkout localmente, o dev precisa iniciar shell, catalog-mfe, admin-mfe e checkout-mfe. O tempo de setup local triplicou. A autonomia do time veio com um overhead de DX que ninguém contabilizou.

Problema 05 — Bundle size — o compartilhamento que não compartilhou: Cada remote empacotou sua própria cópia do Angular Material porque a configuração de shared não incluiu as entradas secundárias (@angular/material/button, @angular/material/dialog). O bundle total ficou 3x maior do que o monolito original.


Solução 01 — Version mismatch

Governança de versões: o Nx como árbitro

O Module Federation permite versões diferentes. O Nx, por padrão, não permite — e essa restrição é um feature, não um bug.

TYPESCRIPT
// ❌ Configuração que causa crashes silenciosos// module-federation.config.ts (remote checkout)export const config: ModuleFederationConfig = {  name: 'checkout',  exposes: {    './Module': './src/app/remote-entry/entry.module.ts',  },  shared: {    // ❌ singleton sem requiredVersion = aceita qualquer versão    // Se shell tem Angular 21 e remote tem Angular 22,    // o Module Federation carrega a versão do shell silenciosamente    '@angular/core': { singleton: true },    '@angular/common': { singleton: true },  },};
TYPESCRIPT
// ✅ Governança de versão explícita com Nx// libs/shared-mf-config/base.config.tsimport { shareAll } from '@nx/angular/module-federation';export const baseConfig = {  shared: {    // ✅ requiredVersion: 'auto' lê do package.json do workspace    // strictVersion: true → falha se versão incompatível    '@angular/core': {      singleton: true,      strictVersion: true,      requiredVersion: 'auto', // ← Nx resolve do root package.json    },    '@angular/router': { singleton: true, strictVersion: true, requiredVersion: 'auto' },    '@angular/forms': { singleton: true, strictVersion: true, requiredVersion: 'auto' },  }};
BASH
# Ver quais apps precisam ser (re)deployados após mudança em libs/authnx affected --target=build --base=main# Output:# Affected projects:#   shell          ← host sempre afetado quando auth muda#   checkout-mfe   ← usa AuthGuard da libs/auth#   admin-mfe      ← usa AuthService da libs/auth# NOT affected:#   catalog-mfe    ← não depende de libs/auth# Quando você ATUALIZA o Angular (major version):nx migrate @angular/core@22nx migrate --run-migrations# Aplica migrações em TODOS os apps do workspace de uma vez

⚠️ A regra do Angular em monorepo Nx Quando você faz upgrade do Angular em um workspace Nx, todos os apps do workspace sobem juntos. Isso é intencional — a alternativa é o inferno de versões mistas. Se dois times precisam de versões diferentes de Angular, eles precisam de repositórios separados — e aí perdem as vantagens do monorepo Nx.


Solução 02 — CSS leaking

Isolamento de estilos: ViewEncapsulation não é suficiente

O ViewEncapsulation do Angular isola estilos de componentes mas não resolve os estilos globais que cada remote define.

CSS
/* ❌ Sem namespace — vaza para o shell e outros remotes */.card { background: #1a1a2e; border-radius: 8px; }.btn  { background: #e94560; color: white; }.overlay { z-index: 1000; }  /* ← conflita com overlays do shell */
CSS
/* ✅ Solução 1: Namespace SCSS por remote *//* checkout-mfe/src/styles.scss */.checkout-mfe {  .card   { background: #1a1a2e; border-radius: 8px; }  .btn    { background: #e94560; color: white; }  .overlay { z-index: 100; }}/* No componente raiz do remote: *//* <div class="checkout-mfe"><router-outlet /></div> *//* ✅ Solução 2: CSS Custom Properties do Design System no shell *//* O shell define as variáveis. Os remotes consomem. Nunca definem. */:root {  --ds-color-primary: #dd0031;  --ds-color-surface: #161b22;  --ds-radius-card: 8px;  --ds-z-overlay: 200;  --ds-z-modal:   300;  --ds-z-toast:   400;}/* Remotes APENAS consomem variáveis — nunca definem novos z-index absolutos */

✅ A regra de ouro de CSS em MFEs O shell é o único dono dos estilos globais. Remotes consomem CSS Custom Properties definidas pelo shell. Nenhum remote define valores absolutos de z-index — esse é o bug mais difícil de debugar em produção, porque z-index depende do contexto de empilhamento e o shell não controla o contexto do remote.


Solução 03 — Estado compartilhado

O estado que destrói o deploy independente

⚠️ O anti-padrão mais comum em MFEs Colocar o estado do usuário autenticado em um NgRx Store compartilhado via Module Federation entre todos os remotes. Parece certo — "é estado global, deve ficar no NgRx global". O problema é que qualquer mudança no shape desse estado exige que todos os remotes sejam atualizados e deployados ao mesmo tempo. O deploy "independente" deixa de ser independente.

TYPESCRIPT
// libs/core/src/lib/event-bus.service.ts// ✅ O Event Bus é o contrato entre remotes — não o shape do estadoimport { Injectable } from '@angular/core';import { Subject, filter, map } from 'rxjs';export interface MfeEvent {  type: string;  payload: unknown;  source: string;}export type UserAuthenticatedEvent = MfeEvent & {  type: 'user.authenticated';  payload: {    userId:   string;    tenantId: string;    // ✅ Apenas os campos que outros remotes REALMENTE precisam  };};export type CartUpdatedEvent = MfeEvent & {  type: 'cart.updated';  payload: { itemCount: number; total: number };};@Injectable({ providedIn: 'root' })export class EventBusService {  private bus$ = new Subject<MfeEvent>();  emit(event: MfeEvent): void {    this.bus$.next(event);  }  on<T extends MfeEvent>(type: T['type']) {    return this.bus$.pipe(      filter(e => e.type === type),      map(e => e as T)    );  }}// ✅ No shell — emite após autenticação// eventBus.emit({ type: 'user.authenticated', payload: { userId, tenantId }, source: 'shell' });// ✅ No checkout-mfe — escuta sem saber nada do shell// eventBus.on<UserAuthenticatedEvent>('user.authenticated').subscribe(...)// Quando o shape do usuário muda, você versiona o evento:// 'user.authenticated.v2' — backward compatible, remotes migram no seu ritmo
TYPESCRIPT
// ✅ Se você PRECISA de estado compartilhado, compartilhe o mínimoexport const AUTH_CONTEXT = new InjectionToken<AuthContext>('AUTH_CONTEXT');export interface AuthContext {  // Apenas o que é genuinamente necessário para TODOS os remotes  userId:   string | null;  tenantId: string | null;  isAuthenticated: boolean;  // ❌ NÃO coloque: permissions, roles, profile, preferences}

Solução 04 — Developer Experience

Desenvolvimento local sem 4 terminais abertos

JSON
// shell/src/assets/module-federation.manifest.json// ✅ Dynamic Module Federation — a URL do remote é resolvida em runtime{  "checkout": "http://localhost:4201",  "catalog":  "http://localhost:4202",  "admin":    "http://localhost:4203"}// shell/src/assets/module-federation.manifest.staging.json{  "checkout": "https://checkout.staging.example.com",  "catalog":  "https://catalog.staging.example.com",  "admin":    "https://admin.staging.example.com"}
BASH
# ✅ Inicia o shell + checkout em modo live# catalog e admin são servidos como static (sem live reload)nx serve shell --devRemotes=checkout# Inicia o shell + dois remotes em modo livenx serve shell --devRemotes=checkout,catalog# Com Dynamic Module Federation — aponta para staging:# O dev desenvolve checkout localmente, catalog e admin vêm do stagingcp src/assets/module-federation.manifest.staging.json \   src/assets/module-federation.manifest.json

→ O padrão Nx recomendado para DX Configure um target serve-with-staging-remotes no project.json do shell. O dev inicia um único terminal, o shell carrega o remote local que está desenvolvendo e os outros remotes do staging. Nenhum mock, nenhum stub — ambiente real com um remote vivo.


Solução 05 — Bundle size

O compartilhamento que não compartilhou nada

TYPESCRIPT
// ❌ Shared incompleto — cada remote tem sua própria cópia do Materialshared: {  '@angular/material': { singleton: true, strictVersion: true },  // Problema: @angular/material/button é uma entrada separada  // → Cada remote vai empacotar sua própria cópia desses módulos  // → Bundle total: shell(mat/button) + checkout(mat/button) = 3x}
TYPESCRIPT
// ✅ shareAll com entradas secundárias — a configuração corretaimport { shareAll } from '@nx/angular/module-federation';// shareAll compartilha AUTOMATICAMENTE todos os pacotes do package.json// Incluindo entradas secundárias como @angular/material/buttonexport const sharedDeps = shareAll({  singleton: true,  strictVersion: true,  requiredVersion: 'auto',});// module-federation.config.ts de cada remote:export const config: ModuleFederationConfig = {  name: 'checkout',  exposes: { './Module': './src/app/remote-entry/entry.module.ts' },  shared: {    ...sharedDeps,  },};// Resultado com shareAll:// Bundle antes: shell(300KB) + checkout(280KB) + catalog(275KB) = 855KB// Bundle depois: shell(300KB) + checkout(80KB) + catalog(75KB) = 455KB// Redução de ~47% no total transferido

✅ Nx bundle analysis integrada Rode nx build checkout --analyze para abrir o Webpack Bundle Analyzer e identificar quais pacotes estão sendo duplicados entre o shell e os remotes. Faça essa análise antes do primeiro deploy em produção — é muito mais difícil corrigir depois que o cache do CDN está propagado.


Quando microfrontends não são a resposta

Critério

Microfrontends

Monolito modular com Nx

Tamanho do time

✅ 5+ times paralelos

✅ 1-3 times — custo de MFE não compensa

Cadência de deploy

✅ Times com cadências diferentes

✅ Mesma cadência — deploy unificado é mais simples

Domínios de negócio

✅ Domínios genuinamente independentes

❌ Muita interdependência — MFE cria fronteiras artificiais

Bundle inicial

❌ Maior — overhead de Module Federation

✅ Menor

Complexidade de config

❌ Alta — versões, CSS, estado, DX

✅ Baixa

DX local

⚠️ Degradada sem configuração cuidadosa

✅ Simples — ng serve

→ A recomendação do Nx A documentação oficial do Nx é direta: "Se você quer otimizar builds e não precisa de deploys independentes, use nosso guia de Faster Builds com Module Federation." Você pode ter builds incrementais, cache inteligente e repositório único sem a complexidade de remotes deployados independentemente. Essa é a resposta certa para a maioria dos times de até 30 pessoas.

O checklist antes de adotar microfrontends

  • ✅ 5+ times que precisam deployar com cadências genuinamente diferentes

  • ✅ Domínios de negócio claros que não compartilham estado além de autenticação

  • ✅ SLAs independentes — uma falha no checkout não deve afetar o catalog

  • ✅ Design system estabelecido via CSS Custom Properties antes de começar

  • ✅ Governança de versões — política clara para upgrades do Angular

  • ❌ Time único que "vai crescer" — antecipe com módulos lazy, não com MFE

  • ❌ Complexidade técnica como objetivo — MFE é para problemas organizacionais

  • ⚠️ Estado compartilhado extenso entre os remotes — redesenhe os limites antes

  • ⚠️ SEO crítico — MFEs com Module Federation são CSR por padrão


Setup correto do zero

Cada passo abaixo mitiga um risco real de produção. Pular um não gera erro imediato — gera o problema que você vai debugar em produção 6 meses depois.

Passo 1 — Criar o workspace Nx com preset Angular:

BASH
npx create-nx-workspace@latest minha-org --preset=angular-monorepo

Risco mitigado: sem o preset correto, o workspace não configura o @nx/enforce-module-boundaries automaticamente. Sem essa regra de lint, remotes vão importar uns dos outros diretamente — criando acoplamento que torna o deploy independente impossível. Você só vai descobrir o problema quando tentar deployar o checkout sem o catalog e o build quebrar.


Passo 2 — Gerar o shell com Dynamic Module Federation:

BASH
nx g @nx/angular:host shell --remotes=checkout,catalog,admin --dynamic=true

Risco mitigado: sem --dynamic=true, a URL de cada remote fica hardcoded no bundle do shell em tempo de build. Mudar a URL de um remote em produção exige rebuild e redeploy do shell inteiro — destruindo a independência de deploy. Com o manifest JSON dinâmico, você muda a URL em runtime sem tocar no shell.


Passo 3 — Criar as libs compartilhadas antes de qualquer feature:

BASH
nx g @nx/angular:lib libs/uinx g @nx/angular:lib libs/authnx g @nx/angular:lib libs/core

Risco mitigado: criar libs depois que os remotes já existem significa que cada remote já tem sua própria versão de componentes e serviços. Unificar depois é uma refatoração dolorosa que os times resistem por meses. Criar antes força a disciplina de "código compartilhado vai para libs" desde o primeiro commit — o acoplamento nunca existe para ser removido.


Passo 4 — Configurar a base de shared config em libs/shared-mf-config:

TYPESCRIPT
// shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto' })

Risco mitigado: sem uma config base centralizada, cada remote define seu próprio shared — e invariavelmente alguém esquece de incluir as entradas secundárias do Angular Material ou do CDK. O resultado é o Problema 05: bundle 3x maior. Com a config base no monorepo, um único PR corrige todos os remotes ao mesmo tempo.


Passo 5 — Estabelecer o Design System no shell antes do primeiro remote: Defina todas as CSS Custom Properties em shell/src/styles.scss e documente a convenção de namespace (ex: .checkout-mfe { ... }).

Risco mitigado: sem tokens CSS centralizados, cada remote define seus próprios valores de cor, espaçamento e z-index. Em 3 meses você tem 4 sistemas de design divergentes e CSS leaking não determinístico (Problema 02). Refatorar isso depois significa convencer 4 times a parar de entregar features para alinhar variáveis CSS — conversa que nunca acontece.


Passo 6 — Implementar o EventBusService antes do primeiro fluxo cross-remote: libs/core/src/lib/event-bus.service.ts com tipos de evento em libs/core/src/lib/events.ts.

Risco mitigado: sem um contrato de comunicação estabelecido, o primeiro dev que precisar passar dados entre remotes vai colocar estado no NgRx global — criando o Problema 03. O EventBus força a equipe a pensar em contratos versionados antes de criar a dependência de estado. É muito mais fácil estabelecer o padrão certo antes do primeiro fluxo do que remover estado global depois de 3 times dependerem dele.


BASH
# Estrutura de workspace que aguenta 2 anos de produçãominha-org/├── apps/│   ├── shell/              # Host — roteamento e layout global│   ├── checkout-mfe/       # Remote — domínio de pagamento│   ├── catalog-mfe/        # Remote — domínio de catálogo│   └── admin-mfe/          # Remote — domínio administrativo├── libs/│   ├── ui/                 # Design System — components, tokens CSS│   ├── auth/               # AuthGuard, AuthContext, EventBus types│   ├── core/               # EventBus, interceptors, error handler│   ├── shared-mf-config/   # Base config de Module Federation│   └── data-access/        # APIs compartilhadas (opcional)├── module-federation.manifest.json          # dev├── module-federation.manifest.staging.json  # staging├── module-federation.manifest.prod.json     # produção└── nx.json

O que realmente importa

O Module Federation resolve problemas técnicos de carregamento federado. O Nx resolve problemas técnicos de build e dependência. Nenhum dos dois resolve o problema real: microfrontends são uma solução para o Conway's Law, não para performance de build.

📝
Entenda o papel do Module Federation e do Nx

Module Federation foca no carregamento federado, enquanto Nx gerencia build e dependências. Nenhuma ferramenta resolve a complexidade intrínseca dos microfrontends.

Microfrontends são eficazes para equipes que precisam trabalhar de forma independente, desde que os domínios de negócio estejam bem definidos.

Sem essa clareza, o projeto sofre com a complexidade distribuída e o acoplamento típico de monolitos, prejudicando o resultado final.

Se o seu problema é que dois times não conseguem trabalhar no mesmo código sem pisar um no outro, microfrontends podem ajudar — mas apenas se os domínios de negócio tiverem fronteiras claras o suficiente para que os remotes sejam genuinamente independentes.

A maioria dos projetos que adota microfrontends sem essa clareza de domínio acaba com o pior dos dois mundos: a complexidade da arquitetura distribuída com o acoplamento do monolito.

A política em uma frase Microfrontends são para times, não para código. Se o driver é "precisamos que times diferentes deployem com cadências diferentes em domínios de negócio claros" — use MFE. Se o driver é "nossa aplicação ficou grande" — use módulos lazy do Angular com Nx incremental builds. A segunda opção resolve 80% dos problemas com 20% da complexidade.

A independência que os microfrontends prometem custa mais do que o tutorial mostra. Só vale pagar quando você sabe exatamente o que está comprando.


Fontes

  • nx.dev/docs/technologies/module-federation — Arquitetura oficial de MFE com Nx: quando usar, version mismatch, deploys afetados, estratégia de shared libs.

  • blog.angular.dev — Manfred Steyer (fev/2025) — "Micro Frontends with Angular and Native Federation" — Module Federation vs Native Federation, singleton sharing, strictVersion, ESM + import maps.

  • DEV Community — Vitalii Petrenko (mai/2025) — "Microfrontends in 2025: A Reality Check from the Trenches" — CSS isolation, state management cross-MFE, version governance.

  • infinum.com (dez/2025) — "Implementing Micro Frontends — What to Look Out For" — shared packages, atualização de Angular em monorepo.

  • angulararchitects.io — "Multi-Framework and -Version Micro Frontends with Module Federation" — requiredVersion strategies, bootstrap assíncrono.

  • javascript-conference.com (jul/2024) — "Microfrontends in the Monorepo" — nx affected:apps para deploy seletivo, enforce-module-boundaries.

Continue explorando
Quer mais conteúdo assim?

Veja outros artigos na mesma linha e mantenha o ritmo.

Ver mais artigos
#JavaScript#TypeScript#Angular

Arsenal do dev de elite.

Next.js 16 PPR: Elimine o tradeoff entre estático e dinâmico e alcance Edge TTFB com dados atualizados
Programação & Dev

Next.js 16 PPR: Elimine o tradeoff entre estático e dinâmico e alcance Edge TTFB com dados atualizados

O Partial Prerendering no Next.js 16 resolve o clássico tradeoff de renderização, entregando velocidade estática com dados dinâmicos em uma resposta.

Genildo Souza · 18 de jul. · 15 min
Pare de arriscar seu frontend: a arquitetura definitiva do Angular 22
Programação & Dev

Pare de arriscar seu frontend: a arquitetura definitiva do Angular 22

O Angular 22 chegou. Descubra como as mudanças arquiteturais e os novos primitivos de reatividade o tornam a escolha ideal para sistemas enterprise.

Genildo Souza · 11 de jul. · 18 min
SolidJS em 2026: Por que a reatividade fina está superando o Virtual DOM
Programação & Dev

SolidJS em 2026: Por que a reatividade fina está superando o Virtual DOM

Sem Virtual DOM e com reatividade fina, o SolidJS redefine o desenvolvimento frontend em 2026 com performance superior e componentes otimizados..

Genildo Souza · 24 de jun. · 28 min
Neste artigo
  • A realidade que o tutorial não mostra
  • Os 5 problemas de produção
  • Solução 01 — Version mismatch
  • Governança de versões: o Nx como árbitro
  • Solução 02 — CSS leaking
  • Isolamento de estilos: ViewEncapsulation não é suficiente
  • Solução 03 — Estado compartilhado
  • O estado que destrói o deploy independente
  • Solução 04 — Developer Experience
  • Desenvolvimento local sem 4 terminais abertos
  • Solução 05 — Bundle size
  • O compartilhamento que não compartilhou nada
  • Quando microfrontends não são a resposta
  • O checklist antes de adotar microfrontends
  • Setup correto do zero
  • O que realmente importa
  • Fontes