O que são microfrontends — e por que usar
Microfrontends aplicam os princípios de microserviços ao front-end: você divide a interface em módulos/autônomos (MFEs) que podem ser desenvolvidos, versionados, implantados e escalados de forma independente. O “app raiz” (host/shell) orquestra e compõe esses módulos em tempo de execução.
Benefícios
- Times autônomos e ciclos de release independentes.
- Escalabilidade organizacional: cada MFE tem seu backlog, deploy e métricas.
- Evolução tecnológica incremental (ex.: um MFE pode migrar biblioteca sem travar o restante).
- Foco por domínio (ex.: Catálogo, Checkout, Conta, etc.).
- Deploys menores e rollback rápido.
Quando faz sentido (e quando não)
Use quando:
- O produto é grande e de longa duração, com múltiplos domínios e vários times.
- Você precisa desacoplar releases e reduzir impacto de mudanças.
- Há migração gradual de uma base legada.
Evite se:
- O time é pequeno, o app é simples, ou o custo de observabilidade, design system e coordenação supera os ganhos.
A escolha técnica deste guia
Para habilitar microfrontends em Next.js, adotaremos o Module Federation, recurso nativo do Webpack que permite compartilhar módulos entre diferentes aplicações em tempo de execução.
Faremos essa integração por meio do plugin @module-federation/nextjs-mf, que atualmente oferece suporte apenas ao Pages Router. Até o momento, o App Router não é suportado oficialmente — essa limitação está documentada no próprio repositório do projeto.
Outro ponto importante: o Turbopack (o novo bundler do Next.js) ainda não possui suporte ao Module Federation. Por esse motivo, neste guia continuaremos utilizando o Webpack, garantindo compatibilidade com a abordagem proposta. Module FederationGitHub
Versões recomendadas (compatíveis entre si)
Para evitar surpresas, fixaremos versões estáveis já conhecidas por funcionarem bem juntas:
-
Node.js: 20.x LTS (amplamente suportado hoje; 18.x já está em EoL).
Node 18 entrou em EoL em 27/03/2025; 20.x está em LTS de manutenção. Node.js
Next.js: 13.4.19 (Pages Router, Webpack 5; compatível com o plugin e sem App Router).
React / React-DOM: 18.2.0.
Module Federation (Next plugin):
@module-federation/nextjs-mf@8.8.38
(ou a última8.x
estável). npmWebpack: 5.x como dependência do projeto (o plugin recomenda usar o “local webpack”). Module Federation
Gerenciador de pacotes: Yarn (v1.x).
Por que Next 13.4.19? Porque apesar do plugin documentar suporte, o App Router não é suportado; com Pages Router do 13.4.x você obtém estabilidade.
O que vamos construir
Três projetos independentes:
- root-shell (host) — orquestra e consome dois MFEs.
- mfe1 — expõe um Header.
- mfe2 — expõe um ProductCard.
Portas fixas para padronizar os comandos:
- root-shell: 3000
- mfe1: 3001
- mfe2: 3002
Passo a passo (na ordem): Root → MFE1 → MFE2
Pré-requisitos
- Node 20.x ativo (
nvm use 20
).- Yarn 1.x.
- (Opcional)
.nvmrc
com20
em cada projeto.
1) Criar o root-shell (host)
# 1. criar projeto base
yarn create next-app root-shell --typescript
cd root-shell
# 2. Fixar versões
yarn add -E next@13.4.19 react@18.2.0 react-dom@18.2.0
# 3. Module Federation plugin + webpack local + utilitários
yarn add -D @module-federation/nextjs-mf@8.8.38 webpack@^5 cross-env
# 4. Criar diretório types
mkdir -p src/types
O plugin orienta ativar NEXT_PRIVATE_LOCAL_WEBPACK=true e ter webpack instalado no projeto. Module Federation
package.json
(trecho): defina as portas e a flag de “local webpack” (recomendada pelo plugin).
{
"name": "root-shell",
"private": true,
"scripts": {
"dev": "cross-env NEXT_PRIVATE_LOCAL_WEBPACK=true next dev -p 3000",
"build": "cross-env NEXT_PRIVATE_LOCAL_WEBPACK=true next build",
"start": "next start -p 3000"
}
}
Renomear o arquivo next.config
mv next.config.ts next.config.js
next.config.js
: configurar o host consumindo remotes mfe1
e mfe2
.
// eslint-disable-next-line @typescript-eslint/no-require-imports
const NextFederationPlugin = require('@module-federation/nextjs-mf');
module.exports = {
reactStrictMode: true,
webpack(config) {
config.plugins.push(
new NextFederationPlugin({
name: 'root',
filename: 'static/chunks/remoteEntry.js',
remotes: {
mfe1: `mfe1@http://localhost:3001/_next/static/chunks/remoteEntry.js`,
mfe2: `mfe2@http://localhost:3002/_next/static/chunks/remoteEntry.js`
},
shared: {},
extraOptions: {
exposePages: false
}
})
);
return config;
}
};
src/types/global.d.ts
(tipos para os módulos remotos):
declare module 'mfe1/header' {
const Header: React.ComponentType<{ title?: string }>;
export default Header;
}
declare module 'mfe2/product-card' {
const ProductCard: React.ComponentType<{ name: string; price: number }>;
export default ProductCard;
}
src/pages/_app.tsx
(padrão Pages Router):
import type { AppProps } from 'next/app';
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
src/pages/index.tsx
— O ssr: false evita Hydration mismatch ao renderizar componentes remotos no cliente.
import dynamic from "next/dynamic";
const Header = dynamic(() => import("mfe1/header"), {
ssr: false,
loading: () => <div>Carregando Header…</div>
});
const ProductCard = dynamic(() => import("mfe2/product-card"), {
ssr: false,
loading: () => <div>Carregando Produto…</div>
});
export default function Home() {
return (
<>
<Header title="Microfrontends com Next.js" />
<main style={{ padding: 24 }}>
<h2>Vitrine</h2>
<ProductCard name="Camiseta Dev" price={99.9} />
</main>
</>
);
}
2) Criar o mfe1 (remote com Header
)
# 1. criar mfe1
yarn create next-app mfe1 --typescript
cd mfe1
# 2. Fixar versões
yarn add -E next@13.4.19 react@18.2.0 react-dom@18.2.0
# 3. Module Federation plugin + webpack local + utilitários
yarn add -D @module-federation/nextjs-mf@8.8.38 webpack@^5 cross-env
# 4. Criar diretório components
mkdir -p src/components
package.json
(trecho):
{
"name": "mfe1",
"private": true,
"scripts": {
"dev": "cross-env NEXT_PRIVATE_LOCAL_WEBPACK=true next dev -p 3001",
"build": "cross-env NEXT_PRIVATE_LOCAL_WEBPACK=true next build",
"start": "next start -p 3001"
}
}
src/components/Header.tsx
:
type Props = { title?: string };
export default function Header({ title = 'MFE1 Header' }: Props) {
return (
<header style={{ padding: 16, background: '#111', color: '#fff' }}>
<h1 style={{ margin: 0, fontSize: 20 }}>{title}</h1>
</header>
);
}
Renomear o arquivo next.config
mv next.config.ts next.config.js
next.config.js
— expor o componente:
// eslint-disable-next-line @typescript-eslint/no-require-imports
const NextFederationPlugin = require('@module-federation/nextjs-mf');
module.exports = {
webpack(config) {
config.plugins.push(
new NextFederationPlugin({
name: 'mfe1',
filename: 'static/chunks/remoteEntry.js',
exposes: {
'./header': './src/components/Header.tsx'
},
shared: {},
extraOptions: {
exposePages: false,
ssr: false
}
})
);
return config;
}
};
src/pages/index.tsx
(para ver o MFE isolado):
import Header from '../components/Header';
export default function Index() {
return (
<><Header title="MFE1 isolado" />
<p style={{ padding: 16 }}>Este é o MFE1 rodando sozinho.</p>
</>
);
}
3) Criar o mfe2 (remote com ProductCard
)
# 1. criar mfe2
yarn create next-app mfe2 --typescript
cd mfe2
# 2. Fixar versões
yarn add -E next@13.4.19 react@18.2.0 react-dom@18.2.0
# 3. Module Federation plugin + webpack local + utilitários
yarn add -D @module-federation/nextjs-mf@8.8.38 webpack@^5 cross-env
# 4. Criar diretório components
mkdir -p src/components
package.json
(trecho):
{
"name": "mfe2",
"private": true,
"scripts": {
"dev": "cross-env NEXT_PRIVATE_LOCAL_WEBPACK=true next dev -p 3002",
"build": "cross-env NEXT_PRIVATE_LOCAL_WEBPACK=true next build",
"start": "next start -p 3002"
}
}
src/components/ProductCard.tsx
:
type Props = { name: string; price: number };
export default function ProductCard({ name, price }: Props) {
return (
<article style={{ border: '1px solid #ddd', borderRadius: 8, padding: 16, width: 280 }}>
<strong style={{ display: 'block', marginBottom: 8 }}>{name}</strong>
<span>R$ {price.toFixed(2)}</span>
</article>
);
}
Renomear o arquivo next.config
mv next.config.ts next.config.js
next.config.js
— expor o componente:
// eslint-disable-next-line @typescript-eslint/no-require-imports
const NextFederationPlugin = require('@module-federation/nextjs-mf');
module.exports = {
webpack(config) {
config.plugins.push(
new NextFederationPlugin({
name: 'mfe2',
filename: 'static/chunks/remoteEntry.js',
exposes: {
'./product-card': './src/components/ProductCard.tsx'
},
shared: {},
extraOptions: {
exposePages: false,
ssr: false
}
})
);
return config;
}
};
src/pages/index.tsx
:
import ProductCard from '../components/ProductCard';
export default function Index() {
return (
<main style={{ padding: 16 }}>
<h1>MFE2 isolado</h1>
<ProductCard name="Livro MF" price={59.9} />
</main>
);
}
Aviso ESLint sobre require()
No next.config.js
dos projetos usamos:
// eslint-disable-next-line @typescript-eslint/no-require-imports
const NextFederationPlugin = require('@module-federation/nextjs-mf');
Por que ocorre a warning?
O ESLint, com a regra @typescript-eslint/no-require-imports
, recomenda usar import
em vez de require()
para manter consistência e suporte a tree-shaking.
Por que não há problema em desabilitar?
No contexto de Next.js + Module Federation, o plugin precisa ser importado via require()
, porque é usado diretamente na configuração do Webpack, que ainda depende do CommonJS.
Sobre tipos TypeScript para MFEs
Para que o TypeScript “entenda” os imports das rotas remotas ('mfe1/header'
, 'mfe2/product-card'
), criamos declarações em src/types/global.d.ts
no host (e, se necessário, nos remotes que consumirem outros remotes). Esse arquivo evita erros de “módulo não encontrado” e centraliza a organização dos tipos.
Como rodar os três projetos (ordem recomendada)
1) Inicie os remotes primeiro (para que o host encontre os remoteEntry.js).
2) Depois suba o host.
No MFE1:
cd mfe1
yarn dev # http://localhost:3001
No MFE2:
cd mfe2
yarn dev # http://localhost:3002
No Root (host):
cd root-shell
yarn dev # http://localhost:3000
Acesse http://localhost:3000 e você verá o Header (MFE1) e o ProductCard (MFE2) renderizados dentro do host.
Produção: use yarn build em cada app. Depois, yarn start -p mantendo as mesmas portas ou atualize os URLs de remotes para o domínio real (CDN/host de produção).
Por que congelamos versões — e a questão do next.config
-
create-next-app
instala a versão mais recente por padrão. Para evitar cair numa combinação Next (recente) + App Router + Turbopack — que não funciona com MF — forçamosnext@13.4.19
(Pages Router). Module Federation -
next.config.js
: os exemplos do plugin usam JS e funcionam muito bem no 13.4.x; já onext.config.ts
passou a ser suportado oficialmente só no Next 15. Como estamos no 13.4.x, manteremos JS para compatibilidade máxima. nextjs.org
Dicas e troubleshooting
-
404 em
remoteEntry.js
: confirme as portas e os caminhos/_next/static/ssr|chunks/remoteEntry.js
nonext.config.js
do host de acordo comisServer
. Module Federation - Versões de Node: se você ainda precisa de Node 18 em dev, saiba que está em EoL; considere planejar migração para 20.x LTS. Node.js
-
Local Webpack: se aparecer erro relacionado a MF/webpack, verifique se a flag
NEXT_PRIVATE_LOCAL_WEBPACK
está ativa ewebpack@5
está nodevDependencies
. Module Federation
Conclusão
Com Module Federation + Next (Pages Router) você obtém microfrontends de verdade: MFEs independentes compondo uma experiência única no host, com autonomia de times e deploys desacoplados. O segredo está em congelar versões compatíveis, por enquanto ficar no Pages Router, usar Webpack 5 (não Turbopack) e configurar os remotes com cuidado.
Top comments (0)