Micro-frontends: Quando e Como Implementar
Depois de implementar micro-frontends em grandes empresas, compartilho aqui quando essa arquitetura realmente vale a pena e como fazer direito.
O que São Micro-frontends?
Micro-frontends estendem o conceito de microserviços para o frontend, permitindo que equipes independentes desenvolvam, testem e deployem partes da aplicação de forma autônoma.
Quando Implementar?
✅ Cenários Ideais:
- Múltiplas equipes (5+ times) trabalhando no mesmo produto
- Tecnologias diferentes necessárias em partes da aplicação
- Deploy independente é prioridade
- Escalabilidade da equipe é um problema
❌ Quando NÃO usar:
- Equipe pequena (menos de 10 devs)
- Aplicação simples sem complexidade de domínio
- Performance é crítica (overhead de rede)
- Shared state complexo entre módulos
Experiência no QuintoAndar
Implementamos micro-frontends para o portal do corretor, onde tínhamos:
- 3 equipes diferentes
- React + Vue.js na mesma aplicação
- Deploys independentes por funcionalidade
Arquitetura Implementada:
// Shell Application (Container)
const RemoteComponent = React.lazy(
() => import("propertyModule/PropertySearch")
);
const App = () => (
<Router>
<Header />
<Routes>
<Route
path="/properties/*"
element={
<Suspense fallback={<Spinner />}>
<RemoteComponent />
</Suspense>
}
/>
<Route path="/profile/*" element={<ProfileModule />} />
</Routes>
<Footer />
</Router>
);
Implementação com Module Federation
1. Configuração do Host (Shell)
// webpack.config.js (Host)
const ModuleFederationPlugin = require("@module-federation/webpack");
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: "shell",
remotes: {
propertyModule: "property@http://localhost:3001/remoteEntry.js",
userModule: "user@http://localhost:3002/remoteEntry.js",
},
shared: {
react: { singleton: true },
"react-dom": { singleton: true },
},
}),
],
};
2. Configuração do Remote (Módulo)
// webpack.config.js (Remote)
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: "property",
filename: "remoteEntry.js",
exposes: {
"./PropertySearch": "./src/PropertySearch",
"./PropertyDetails": "./src/PropertyDetails",
},
shared: {
react: { singleton: true },
"react-dom": { singleton: true },
},
}),
],
};
3. Componente Exportado
// PropertySearch.tsx (Remote)
import React from "react";
interface PropertySearchProps {
onSearch?: (query: string) => void;
theme?: "light" | "dark";
}
const PropertySearch: React.FC<PropertySearchProps> = ({
onSearch,
theme = "light",
}) => {
return (
<div className={`search-container ${theme}`}>
{/* Implementação do componente */}
</div>
);
};
export default PropertySearch;
Patterns de Comunicação
1. Event Bus Pattern
// events.ts
class EventBus {
private events: { [key: string]: Function[] } = {};
subscribe(event: string, callback: Function) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
emit(event: string, data?: any) {
if (this.events[event]) {
this.events[event].forEach((callback) => callback(data));
}
}
unsubscribe(event: string, callback: Function) {
if (this.events[event]) {
this.events[event] = this.events[event].filter((cb) => cb !== callback);
}
}
}
export const eventBus = new EventBus();
2. Shared State com Context
// SharedContext.tsx
interface SharedState {
user: User | null;
theme: "light" | "dark";
language: string;
}
const SharedContext = React.createContext<SharedState | null>(null);
// No Shell App
const App = () => {
const [sharedState, setSharedState] = useState<SharedState>({
user: null,
theme: "light",
language: "pt-BR",
});
return (
<SharedContext.Provider value={sharedState}>
{/* Micro-frontends aqui */}
</SharedContext.Provider>
);
};
Desafios e Soluções
1. Versionamento de Dependências
Problema: Conflitos entre versões do React/Vue
Solução:
// webpack.config.js
shared: {
react: {
singleton: true,
requiredVersion: '^18.0.0',
strictVersion: true
},
}
2. Roteamento Global
Problema: Navegação entre micro-frontends
Solução:
// Router.tsx
import { createBrowserHistory } from "history";
// Histórico compartilhado
export const sharedHistory = createBrowserHistory();
// Cada micro-frontend usa o mesmo histórico
const MicroFrontendRouter = ({ children }) => (
<Router navigator={sharedHistory}>{children}</Router>
);
3. Design System Compartilhado
Problema: Consistência visual
Solução:
// Design System como micro-frontend separado
const designSystem = {
name: "designSystem",
exposes: {
"./Button": "./src/Button",
"./Input": "./src/Input",
"./Theme": "./src/Theme",
},
};
// Usado por todos os micro-frontends
import { Button } from "designSystem/Button";
Performance Considerations
Bundle Splitting Inteligente
// webpack.config.js
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
priority: 10,
},
shared: {
name: 'shared',
minChunks: 2,
priority: 5,
},
},
},
}
Lazy Loading Otimizado
// Preload estratégico
const PropertyModule = React.lazy(() => {
// Preload quando usuário hovera no menu
const preload = () => import("propertyModule/PropertySearch");
return import("propertyModule/PropertySearch");
});
// Uso com preload
<MenuItem
onMouseEnter={() => PropertyModule.preload?.()}
onClick={() => navigate("/properties")}
>
Imóveis
</MenuItem>;
Monitoramento e Debug
Error Boundaries por Módulo
class MicroFrontendErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, moduleName: props.moduleName };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// Log específico por módulo
analytics.track("Micro-frontend Error", {
module: this.state.moduleName,
error: error.message,
stack: errorInfo.componentStack,
});
}
render() {
if (this.state.hasError) {
return (
<div>
<h2>Algo deu errado no módulo {this.state.moduleName}</h2>
<button onClick={() => window.location.reload()}>
Recarregar página
</button>
</div>
);
}
return this.props.children;
}
}
Resultados Alcançados
No QuintoAndar:
- Deploy time: 45min → 8min por módulo
- Time to market: -60% para novas features
- Developer experience muito melhor
- Autonomia total das equipes
No Netshoes:
- 3 tecnologias diferentes na mesma app
- Zero downtime em deploys parciais
- A/B tests por módulo independente
Ferramentas Recomendadas
- Webpack Module Federation - Standard do mercado
- Single SPA - Framework agnóstico
- Bit - Component sharing
- Nx - Monorepo com micro-frontends
Conclusão
Micro-frontends não são bala de prata. São uma solução arquitetural para problemas específicos de escala e autonomia.
Implemente quando:
- Múltiplas equipes precisam de autonomia
- Deploy independente é crítico
- Tecnologias diferentes são necessárias
Próximo passo: Avalie se sua aplicação realmente precisa dessa complexidade antes de implementar.
Você já trabalhou com micro-frontends? Qual foi sua experiência? Compartilhe nos comentários!