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: