Na primeira parte, vimos como criar uma arquitetura de Micro Frontends utilizando Angular 21 e Native Federation.
Nesta segunda parte, vamos aprofundar a integração entre Host e Remote, entendendo o que acontece por trás da configuração e, principalmente, alguns pontos que podem gerar erros durante a implementação.
A ideia é sair de uma configuração que simplesmente "funciona" para entender por que ela funciona.
1. Arquitetura que vamos construir
Teremos dois projetos:
angular-micro-frontends/ │ ├── mfe-host-2/ │ └── Angular 21 │ └── Host │ └── mfe-remote-2/ └── Angular 21 └── Remote Durante o desenvolvimento:
Host http://localhost:4200 Remote http://localhost:4201 O fluxo será:
HOST localhost:4200 │ │ ▼ federation.manifest.json │ │ ▼ Remote :4201 │ ▼ remoteEntry.json │ ▼ ./Component │ ▼ App O ponto importante é entender que o Host não importa diretamente o arquivo app.ts do Remote.
Ele conhece apenas o nome do Remote e o módulo que o Remote decidiu expor.
2. O papel do federation.config.js
No Remote temos:
const { withNativeFederation, shareAll } = require('@angular-architects/native-federation/config'); module.exports = withNativeFederation({ name: 'mfe-remote-2', exposes: { './Component': './src/app/app.ts', }, shared: { ...shareAll({ singleton: true, strictVersion: true, requiredVersion: 'auto', }), }, skip: [ 'rxjs/ajax', 'rxjs/fetch', 'rxjs/testing', 'rxjs/webSocket', ], features: { ignoreUnusedDeps: true, }, }); Aqui temos uma das partes mais importantes da configuração:
exposes: { './Component': './src/app/app.ts', } Isso significa:
"Quando outro aplicativo solicitar
./Component, entregue o módulo localizado em./src/app/app.ts."
O nome ./Component é um nome de exposição, não precisa ser igual ao nome do arquivo.
Por exemplo, também poderíamos ter:
exposes: { './Products': './src/app/pages/products/products.ts', } E no Host:
loadRemoteModule('mfe-remote-2', './Products') 3. ./Component não é o nome da classe
Esse é um ponto que pode gerar bastante confusão.
Temos:
exposes: { './Component': './src/app/app.ts', } e dentro do arquivo:
export class App { } No Host:
loadRemoteModule('mfe-remote-2', './Component') .then((m) => m.App); São três coisas diferentes:
mfe-remote-2 │ └── nome do Remote ./Component │ └── nome da exposição App │ └── exportação JavaScript/TypeScript Podemos visualizar assim:
Host │ │ mfe-remote-2 │ ▼ Remote │ │ ./Component │ ▼ src/app/app.ts │ │ export class App │ ▼ m.App Portanto, se seu componente se chama:
export class App { } o correto é:
.then((m) => m.App) e não:
.then((m) => m.AppComponent) 4. O papel do federation.manifest.json
No Host criamos:
public/ └── assets/ └── federation.manifest.json Com:
{ "mfe-remote-2": "http://localhost:4201/remoteEntry.json" } Esse arquivo funciona como um catálogo dos Remotes que o Host conhece.
Podemos ter vários:
{ "mfe-1": "http://localhost:4201/remoteEntry.json", "mfe-2": "http://localhost:4202/remoteEntry.json", "mfe-3": "http://localhost:4203/remoteEntry.json" } Isso facilita bastante a configuração de uma arquitetura com vários Micro Frontends.
5. Cuidado com o caminho do manifest
No main.ts do Host:
import { initFederation } from '@angular-architects/native-federation'; initFederation('/assets/federation.manifest.json') .catch((err) => console.error(err)) .then(() => import('./bootstrap')) .catch((err) => console.error(err)); Prefira:
/assets/federation.manifest.json em vez de:
../assets/federation.manifest.json O motivo é simples: o primeiro é resolvido a partir da raiz da aplicação.
Se o Host estiver rodando em:
http://localhost:4200 o navegador buscará:
http://localhost:4200/assets/federation.manifest.json Podemos validar isso diretamente no navegador.
Se retornar:
{ "mfe-remote-2": "http://localhost:4201/remoteEntry.json" } o manifest está sendo servido corretamente.
6. remoteEntry.json é diferente do antigo remoteEntry.js
Quem está acostumado com Webpack Module Federation pode estranhar isso.
No modelo tradicional era comum encontrar:
remoteEntry.js No Native Federation trabalhamos com:
remoteEntry.json Por exemplo:
http://localhost:4201/remoteEntry.json Esse arquivo contém as informações necessárias para que o runtime do Native Federation descubra os módulos expostos pelo Remote.
Por isso, uma boa estratégia de debug é sempre testar:
http://localhost:4201/remoteEntry.json Se ele não abrir, não adianta investigar a rota do Host ainda.
Primeiro precisamos resolver o Remote.
7. A rota do Host
No Angular 21 podemos utilizar componentes standalone diretamente:
import { loadRemoteModule } from '@angular-architects/native-federation'; import { Routes } from '@angular/router'; export const routes: Routes = [ { path: 'mfe-1', loadComponent: () => loadRemoteModule('mfe-remote-2', './Component') .then((m) => m.App), }, ]; Quando o usuário acessa:
http://localhost:4200/mfe-1 o Angular executa:
/mfe-1 │ ▼ loadComponent() │ ▼ loadRemoteModule() │ ▼ mfe-remote-2 │ ▼ ./Component │ ▼ App 8. path precisa bater com o link
Um erro simples, mas fácil de passar despercebido.
Se temos:
{ path: 'mfe-2', loadComponent: () => loadRemoteModule('mfe-remote-2', './Component') .then((m) => m.App), } o link precisa ser:
<a routerLink="/mfe-2"> Acesse MFE-2 </a> Não:
<a routerLink="/mfe-1"> Acesse MFE-2 </a> Parece óbvio, mas em uma aplicação com vários MFEs esse tipo de erro pode fazer parecer que o Federation não está funcionando.
9. Não esquecer o RouterOutlet
O Host precisa ter onde renderizar o componente remoto.
No app.html:
<div> <h1>Angular 21 - Micro Frontends</h1> <nav> <a routerLink="/mfe-1"> Acesse MFE-1 </a> <br /> <a routerLink="/mfe-2"> Acesse MFE-2 </a> </nav> </div> <hr /> <router-outlet></router-outlet> E como estamos usando standalone components:
import { Component } from '@angular/core'; import { RouterLink, RouterOutlet } from '@angular/router'; @Component({ selector: 'app-root', imports: [ RouterLink, RouterOutlet ], templateUrl: './app.html', styleUrl: './app.css' }) export class App { } Não basta configurar:
provideRouter(routes) Também precisamos que o template possua:
<router-outlet></router-outlet> 10. Não esquecer o provideRouter
No app.config.ts:
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core'; import { provideRouter } from '@angular/router'; import { routes } from './app.routes'; export const appConfig: ApplicationConfig = { providers: [ provideBrowserGlobalErrorListeners(), provideRouter(routes) ] }; A cadeia completa fica:
main.ts │ ▼ bootstrap │ ▼ app.config.ts │ ▼ provideRouter(routes) │ ▼ app.routes.ts │ ▼ loadRemoteModule() Se qualquer uma dessas partes estiver faltando, a rota pode não funcionar.
11. O Remote também pode funcionar sozinho
Uma das características mais interessantes dessa arquitetura é que o Remote não precisa existir somente dentro do Host.
Podemos acessar:
http://localhost:4201/ diretamente.
O mesmo componente:
export class App { } pode ser usado como aplicação independente.
E também:
http://localhost:4200/mfe-1 como Micro Frontend.
Visualmente:
App │ ┌───────┴────────┐ │ │ ▼ ▼ Acesso direto Federation :4201 :4200/mfe-1 Isso é muito útil durante o desenvolvimento porque o time responsável pelo MFE pode trabalhar sem precisar subir toda a aplicação Host.
12. SSE: o que significa?
Durante o desenvolvimento podemos encontrar no terminal:
[Federation SSE] Client connected. Active connections: 1 Isso é esperado.
SSE significa:
Server-Sent Events.
O Native Federation utiliza esse mecanismo para comunicação entre o servidor de desenvolvimento e o navegador.
Uma mensagem como:
[Federation SSE] Client connected. significa que o navegador conseguiu estabelecer a conexão.
Portanto, essa mensagem isoladamente não representa um erro.
Se aparecer:
[Federation SSE] Client connected. temos uma indicação positiva de que o canal de desenvolvimento foi estabelecido.
13. Como fazer troubleshooting
Quando o MFE não aparece, evite alterar várias configurações ao mesmo tempo.
Faça os testes em ordem.
Passo 1 — Remote
Abra:
http://localhost:4201/ O Remote deve funcionar sozinho.
Passo 2 — remoteEntry
Abra:
http://localhost:4201/remoteEntry.json Deve retornar o metadata do Remote.
Passo 3 — Manifest
Abra:
http://localhost:4200/assets/federation.manifest.json Deve retornar:
{ "mfe-remote-2": "http://localhost:4201/remoteEntry.json" } Passo 4 — Host
Abra:
http://localhost:4200/ ou a rota correspondente:
http://localhost:4200/mfe-1 Passo 5 — DevTools
Abra:
F12 e verifique a aba:
Network Procure por:
federation.manifest.json remoteEntry.json Se ambos carregarem, o próximo ponto a investigar é a resolução do módulo exposto.
14. Um erro importante: 404 no manifest
Um erro como:
GET http://localhost:4200/assets/federation.manifest.json 404 significa que o navegador não encontrou o manifest.
Uma configuração comum no Angular 21 é:
"assets": [ { "glob": "**/*", "input": "public" } ] Nesse caso, o arquivo precisa estar em:
public/assets/federation.manifest.json e não necessariamente em:
src/assets/ Portanto:
mfe-host-2/ │ ├── public/ │ └── assets/ │ └── federation.manifest.json │ └── src/ └── app/ 15. CSS em Micro Frontends
Outro ponto importante é o isolamento dos estilos.
Imagine que o Remote tenha:
src/ └── app/ ├── app.ts ├── app.html └── app.css Podemos utilizar:
@Component({ selector: 'app-root', imports: [], templateUrl: './app.html', styleUrl: './app.css' }) export class App { } A recomendação para um MFE é priorizar estilos associados aos próprios componentes.
Por exemplo:
.container { padding: 20px; } .title { font-size: 32px; } Evite, quando possível, que o MFE altere globalmente:
body { ... } * { ... } html { ... } porque o Remote pode ser executado dentro de outro aplicativo.
16. Por que evitar CSS global no MFE?
Imagine:
Host │ ├── Header ├── Menu │ └── MFE │ └── styles.css Se o MFE possui:
body { margin: 0; background: black; } esse estilo pode interferir na aplicação Host.
Por isso, uma boa regra é:
O Host controla o layout global da aplicação. O MFE controla o visual dos componentes que ele entrega.
Isso permite que o mesmo MFE seja executado:
localhost:4201 ou:
localhost:4200/mfe-1 sem depender de um CSS global específico do Host.
17. Um Remote pode expor mais de um componente
Não precisamos expor apenas o App.
Podemos ter:

