

Junior Alves
Senior Developer
Foto: Unsplash
Atualizado: 14 de julho de 2025 às 08:39
Leitura: 8 minutos de leitura
Criado: 10 de julho de 2025
Microfrontends na Prática - Construindo seu Primeiro Projeto do Zero
Um guia passo a passo para criar um Shell e um Remote com Module Federation, TypeScript e RSBuild.
Introdução
Você já ouviu falar de microfrontends? Talvez já tenha lido sobre como eles prometem resolver as dores de cabeça dos grandes projetos monolíticos, trazendo autonomia para os times e agilidade para os deploys. A teoria é fantástica, mas muitas vezes o que falta é um exemplo prático, um "Hello World" que nos mostre como as peças realmente se encaixam.
É exatamente isso que faremos hoje.
Neste post, vamos colocar a mão no código e construir, do zero, uma aplicação com microfrontends usando um setup com TypeScript, pnpm e RSBuild (um build tool de alta performance que usa Rspack/Webpack por baixo dos panos). Ao final, você terá:
- Um Shell (ou Host), nossa aplicação principal.
- Um Microfrontend (ou Remote), uma aplicação independente.
- Ambos se comunicando e compartilhando dependências em tempo de execução com a mágica do Module Federation.
Este guia é para você, dev frontend que quer dar o próximo passo, na prática.
Refrescando a Memória
Antes de codificar, vamos alinhar dois conceitos-chave:
- A Arquitetura: Pense em microfrontends como "Lego". Em vez de uma única peça gigante e imutável (o monolito), temos vários blocos menores e independentes (o MFE de busca, o MFE de perfil, etc.). O Shell é a base onde encaixamos esses blocos.
- A Tecnologia (Module Federation): É o que permite que essa "montagem de Lego" aconteça dinamicamente no navegador. Ele possibilita que uma aplicação (o Shell) carregue um módulo de outra aplicação em tempo de execução, e o mais importante, que elas compartilhem dependências (como o React) para não duplicar o código.
Com isso em mente, vamos ao que interessa.
Parte 1: Preparando o Terreno (Nosso Monorepo)
A forma mais "simples" de trabalhar é com um monorepo. Vamos criar uma estrutura de pastas simples.
# Crie a pasta principal do projeto
mkdir my-microfrontend
cd my-microfrontend
# Crie a pasta que conterá nossos projetos
mkdir packages
cd packages
# Crie as pastas para o shell e nosso primeiro MFE
mkdir shell
mkdir mfe-calculator
Sua estrutura deve ser esta:
my-microfrontend/
└── packages/
├── mfe-calculator/
└── shell/
Parte 2: Construindo o Orquestrador (O Shell/Host)
O Shell é a nossa aplicação principal, o "orquestrador" que irá carregar os outros.
1. Vá até a pasta e inicie o projeto:
cd packages/shell
pnpm init
2. Instale as dependências:
# Dependências de desenvolvimento (RSBuild e tipos)
pnpm add -D @rsbuild/core @rsbuild/plugin-react @types/react @types/react-dom @types/node
# Dependências do React
pnpm add react react-dom
3. Crie os arquivos de configuração e código:
Crie os arquivos rsbuild.config.mjs
, tsconfig.json
, index.html
e a pasta src
com index.tsx
e App.tsx
dentro.
rsbuild.config.mjs
Este é o cérebro do nosso Shell.
import { defineConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';
export default defineConfig({
plugins: [pluginReact()],
server: {
port: 3000, // O Shell rodará na porta 3000
},
moduleFederation: {
name: 'shell',
// Define quais MFEs este Shell pode carregar
remotes: {
'mfe_frete': 'mfe_frete@http://localhost:3001/remoteEntry.js',
},
// Define as dependências que serão compartilhadas
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
},
});
tsconfig.json
{
"compilerOptions": {
"target": "ESNext",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"module": "ESNext",
"moduleResolution": "bundler",
"jsx": "react-jsx",
"strict": true,
"esModuleInterop": true
},
"include": ["src"]
}
src/App.tsx
Por enquanto, nosso App
será bem simples.
import React from 'react';
export const App = () => {
return (
<div style={{ border: '2px dashed navy', padding: '20px' }}>
<h1>Host: Aplicação Principal (Shell)</h1>
<hr />
<h2>Área para o Microfrontend:</h2>
{/* O MFE será renderizado aqui em breve */}
</div>
);
};
src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-g">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Shell</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
src/index.tsx
import { createRoot } from 'react-dom/client';
import { App } from './App';
const root = createRoot(document.getElementById('root')!);
root.render(<App />);
(Não se esqueça de criar o index.html
e o src/index.tsx
com o código padrão para renderizar o componente App
).
4. Adicione o script de dev no package.json
:
"scripts": {
"dev": "rsbuild dev"
}
Parte 3: Criando Nosso Primeiro Microfrontend (O Remote)
Agora, vamos criar o mfe-calculator
. O processo é quase idêntico.
1. Navegue para a pasta e instale as dependências:
cd ../mfe-calculator # Volte para a pasta packages e entre na mfe-calculator
pnpm init
pnpm add -D @rsbuild/core @rsbuild/plugin-react @types/react @types/react-dom @types/node
pnpm add react react-dom
2. Crie a mesma estrutura de arquivos: rsbuild.config.mjs
, tsconfig.json
, index.html
, src/index.tsx
e src/App.tsx
. Além deles, crie o componente que vamos exportar: src/ShippingCalculator.tsx
.
rsbuild.config.mjs
A configuração do Remote é um pouco diferente. Em vez de remotes
, usamos exposes
.
import { defineConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';
export default defineConfig({
plugins: [pluginReact()],
server: {
port: 3001, // O MFE de frete rodará na porta 3001
},
moduleFederation: {
name: 'mfe_calculator',
// Define o que este MFE vai expor para o mundo
exposes: {
// O apelido './Calculator' aponta para o nosso componente
'./Calculator': './src/ShippingCalculator.tsx',
},
shared: {
react: { singleton: true },
'react-dom': { singleton: true },
},
},
});
src/ShippingCalculator.tsx
Este é o componente que o Shell irá importar.
import { useState } from 'react';
const ShippingCalculator = () => {
const [cep, setCep] = useState('');
return (
<div style={{ border: '2px solid green', padding: '20px' }}>
<h3>Remote: Componente de Frete</h3>
<p>Este conteúdo veio da porta 3001!</p>
<input
type="text"
placeholder="Digite o CEP"
value={cep}
onChange={(e) => setCep(e.target.value)}
/>
<button onClick={() => alert(`Calculando para ${cep}...`)}>
Calcular
</button>
</div>
);
};
export default ShippingCalculator;
(Lembre-se de adicionar o script "dev": "rsbuild dev"
no package.json
deste projeto também).
Parte 4: A Mágica Acontece - Conectando Tudo
Agora, vamos fazer os dois projetos conversarem.
1. Inicie os dois servidores:
Abra dois terminais.
- No terminal 1:
cd packages/shell && pnpm dev
- No terminal 2:
cd packages/mfe-calculator && pnpm dev
2. Consuma o Componente no Shell:
Volte ao arquivo src/App.tsx
do Shell e modifique-o para importar e renderizar o nosso MFE. Como o carregamento é assíncrono, usaremos React.lazy
e Suspense
.
// shell/src/App.tsx
import React, { Suspense } from 'react';
// Importa dinamicamente o componente exposto pelo nosso MFE
const RemoteCalculator = React.lazy(() => import('../../mfe-calculator/src/ShippingCalculator'));
export const App = () => {
return (
<div style={{ border: '2px dashed navy', padding: '20px' }}>
<h1>Host: Aplicação Principal (Shell)</h1>
<hr />
<h2>Área para o Microfrontend:</h2>
<Suspense fallback={<div>Carregando...</div>}>
<RemoteCalculator />
</Suspense>
</div>
);
};
Abra seu navegador em http://localhost:3000
. Você deverá ver o seu Shell renderizando perfeitamente o componente que está sendo servido pela porta 3001!
Seção Bônus: Resolvendo o Erro Invalid hook call
É muito provável que, ao rodar o projeto acima, você se depare com um erro sobre "Invalid hook call" e "múltiplas cópias do React" (no console do browser). Isso não é um erro seu, mas um efeito colateral de como o pnpm
usa links simbólicos em um monorepo.
A solução é ser mais explícito na configuração do Module Federation, forçando a resolução para um caminho único.
A correção definitiva (aplique nos dois arquivos rsbuild.config.mjs
):
import { defineConfig } from '@rsbuild/core';
import { pluginReact } from '@rsbuild/plugin-react';
import deps from './package.json' with { type: 'json' }; // Importe as dependências
import path from 'path'; // Adicione o import do 'path'
export default defineConfig({
// ... plugins e server ...
// Adicione esta seção 'tools'
tools: {
rspack(config) {
config.resolve = config.resolve || {};
config.resolve.alias = config.resolve.alias || {};
config.resolve.alias.react = path.resolve('./node_modules/react');
config.resolve.alias['react-dom'] = path.resolve('./node_modules/react-dom');
return config;
},
},
moduleFederation: {
// ... name, remotes/exposes ...
// Vamos melhorar um pouco nosso shared...
shared: {
react: {
singleton: true,
requiredVersion: deps.dependencies.react,
eager: true,
},
'react-dom': {
singleton: true,
requiredVersion: deps.dependencies['react-dom'],
eager: true,
},
},
},
});
Testando seu entendimento
Após aplicar essa correção, delete as pastas node_modules
e o pnpm-lock.yaml
, rode pnpm install
na raiz e inicie os servidores novamente. O erro desaparecerá.
Conclusão
Parabéns! Você construiu e debugou uma aplicação com microfrontends. Você viu na prática como o Shell e o Remote são independentes, como o contrato é definido via remoteEntry.js
e como resolver um dos problemas mais comuns do mundo real.
A partir daqui, o céu é o limite. Você pode experimentar adicionar um novo MFE, implementar roteamento entre eles ou explorar estratégias de deploy para um bucket na nuvem.
Espero que este guia prático tenha desmistificado o processo e te dado a confiança para explorar mais os microfrontends.
Curtiu? Compartilhe esse post: