Monorepos com Nx e Turborepo: Como Escalar Projetos JavaScript em 2025
Olá HaWkers, monorepos tornaram-se o padrão de fato para projetos JavaScript de larga escala em 2025. Google, Facebook, Microsoft, Uber e praticamente todas as big techs gerenciam milhões de linhas de código em repositórios únicos.
Por quê? Porque monorepos com ferramentas modernas como Nx e Turborepo resolvem problemas que eram impossíveis de resolver com multi-repos: compartilhamento de código sem friction, builds incrementais que economizam horas, e refatoração atômica cross-projects.
Você ainda está gerenciando 15 repositórios separados que compartilham 80% do código? Está na hora de evoluir. Vamos explorar como monorepos modernos mudaram o jogo.
O Problema Que Monorepos Resolvem
Imagine: você tem um e-commerce com frontend web, mobile app, admin panel, APIs, e microserviços. No modelo tradicional (multi-repo):
Cenário Multi-Repo:
- 8 repositórios diferentes
- Cada um com seu package.json, CI/CD, versionamento
- Código duplicado em todos (utils, types, constants)
- Atualizar uma dependência? 8 PRs separados
- Refatorar uma interface compartilhada? Coordenação de pesadelo
- Builds independentes sem otimização
Cenário Monorepo (2025):
- 1 repositório
- Código compartilhado sem duplicação
- Atualizar dependência? 1 commit
- Refatoração atômica de toda a codebase
- Builds inteligentes que só recompilam o que mudou
- Cache distribuído compartilhado entre desenvolvedores
// Estrutura típica de Monorepo em 2025
monorepo-ecommerce/
├── apps/
│ ├── web/ // Next.js app
│ ├── mobile/ // React Native
│ ├── admin/ // Admin panel
│ └── api/ // Express API
├── packages/
│ ├── ui/ // Shared components
│ ├── utils/ // Shared utilities
│ ├── types/ // Shared TypeScript types
│ ├── config/ // Shared configs
│ └── data-access/ // Shared API clients
├── tools/
│ ├── scripts/ // Build scripts
│ └── generators/ // Code generators
├── nx.json // Nx configuration
├── turbo.json // Turborepo configuration
└── package.json // Root package.json
// Antes (multi-repo): Código duplicado em 8 lugares
// packages/web/src/utils/formatCurrency.ts
// packages/mobile/src/utils/formatCurrency.ts
// packages/admin/src/utils/formatCurrency.ts
// ... (5 cópias mais)
// Depois (monorepo): Uma única fonte de verdade
// packages/utils/src/formatCurrency.ts
// Usado por TODOS os apps sem duplicaçãoNx vs Turborepo: Qual Escolher?
Ambos dominam o mercado de 2025, mas têm filosofias diferentes:
Nx: O Framework Completo
# Criando workspace Nx
npx create-nx-workspace@latest my-monorepo
# Nx é opinativo e fornece tudo out-of-the-boxVantagens do Nx:
- Generators: Scaffolding automatizado de apps/libs
- Dependency graph: Visualização clara de dependências
- Affected commands: Roda apenas testes de código afetado
- Plugin ecosystem: Integrações prontas (React, Next, Nest, etc)
- Smart rebuilds: Cache inteligente local e remoto
// nx.json - Configuração Nx
{
"tasksRunnerOptions": {
"default": {
"runner": "nx-cloud",
"options": {
"cacheableOperations": ["build", "test", "lint"],
"accessToken": "your-token",
"parallel": 3
}
}
},
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"inputs": ["production", "^production"]
},
"test": {
"inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"]
}
}
}
// Comandos poderosos
// Roda apenas testes afetados por mudanças
nx affected:test
// Builda apenas apps afetados
nx affected:build
// Visualiza dependency graph
nx graph
// Gera novo componente
nx generate @nx/react:component Button --project=ui
Turborepo: Minimalista e Rápido
# Criando workspace Turborepo
npx create-turbo@latestVantagens do Turborepo:
- Simplicidade: Configuração minimal
- Performance extrema: Builds paralelos agressivos
- Remote caching: Vercel integração nativa
- Zero config: Funciona com qualquer estrutura
- Flexibilidade: Sem opinions fortes
// turbo.json - Configuração Turborepo
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"],
"cache": true
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"],
"cache": true
},
"lint": {
"outputs": [],
"cache": true
},
"dev": {
"cache": false,
"persistent": true
}
},
"globalDependencies": [
"tsconfig.json",
".eslintrc.js"
],
"remoteCache": {
"signature": true
}
}# Comandos Turborepo
# Build todos os packages
turbo build
# Build apenas o que mudou (incremental)
turbo build --filter=...HEAD
# Build com remote cache
turbo build --api="https://api.vercel.com" --token="your-token"
# Run em paralelo com limite
turbo build --concurrency=10O Poder do Incremental Build
O diferencial dos monorepos modernos: nunca rebuild o que não mudou.
// Exemplo prático: E-commerce com 10 packages
// Cenário 1: SEM incremental build
// Desenvolvedor muda 1 linha em packages/utils
// Build tradicional:
// ❌ Rebuilds: web (5min) + mobile (8min) + admin (4min) + api (3min)
// ❌ Total: 20 minutos
// Cenário 2: COM Nx/Turborepo
// Mesmo change em packages/utils
// Build inteligente:
// ✅ Rebuilds apenas: utils (10s) + apps que dependem dele
// ✅ Usa cache para resto
// ✅ Total: 2 minutos
// Economia: 18 minutos = 90% mais rápido!
Implementação Prática: Remote Caching
// packages/build-tools/cache-utils.ts
import { createHash } from 'crypto';
import { readFileSync } from 'fs';
export class BuildCache {
private cacheEndpoint: string;
constructor(endpoint: string) {
this.cacheEndpoint = endpoint;
}
async getCachedBuild(inputs: string[]): Promise<Buffer | null> {
// Gera hash dos inputs
const hash = this.hashInputs(inputs);
try {
// Busca no cache remoto
const response = await fetch(`${this.cacheEndpoint}/cache/${hash}`);
if (response.ok) {
console.log(`✅ Cache HIT for ${hash}`);
return Buffer.from(await response.arrayBuffer());
}
console.log(`❌ Cache MISS for ${hash}`);
return null;
} catch (error) {
console.warn('Cache fetch failed:', error);
return null;
}
}
async storeBuild(inputs: string[], output: Buffer): Promise<void> {
const hash = this.hashInputs(inputs);
try {
await fetch(`${this.cacheEndpoint}/cache/${hash}`, {
method: 'PUT',
body: output,
headers: {
'Content-Type': 'application/octet-stream'
}
});
console.log(`✅ Stored build in cache: ${hash}`);
} catch (error) {
console.warn('Cache store failed:', error);
}
}
private hashInputs(inputs: string[]): string {
const hash = createHash('sha256');
for (const input of inputs) {
const content = readFileSync(input);
hash.update(content);
}
return hash.digest('hex');
}
async executeWithCache<T>(
inputs: string[],
buildFn: () => Promise<T>
): Promise<T> {
// Tenta buscar do cache
const cached = await this.getCachedBuild(inputs);
if (cached) {
return JSON.parse(cached.toString()) as T;
}
// Se não encontrou, executa build
const result = await buildFn();
// Armazena no cache
await this.storeBuild(inputs, Buffer.from(JSON.stringify(result)));
return result;
}
}
// Uso em build script
const cache = new BuildCache('https://cache.mycompany.com');
async function buildPackage(packageName: string) {
const inputs = [
`packages/${packageName}/src/**/*.ts`,
`packages/${packageName}/package.json`,
`packages/${packageName}/tsconfig.json`
];
return cache.executeWithCache(inputs, async () => {
console.log(`🔨 Building ${packageName}...`);
// Build real aqui
const result = await runBuild(packageName);
return result;
});
}Dependency Graph: Entendendo Impactos
// packages/dependency-analyzer/graph.ts
import { ProjectGraph } from '@nx/devkit';
class DependencyAnalyzer {
constructor(private graph: ProjectGraph) {}
findAffectedProjects(changedFiles: string[]): Set<string> {
const affected = new Set<string>();
// Para cada arquivo mudado
for (const file of changedFiles) {
const project = this.findProjectByFile(file);
if (project) {
// Adiciona projeto diretamente afetado
affected.add(project);
// Adiciona todos os projetos que dependem dele
const dependents = this.findDependents(project);
dependents.forEach(dep => affected.add(dep));
}
}
return affected;
}
findDependents(projectName: string): Set<string> {
const dependents = new Set<string>();
// Busca todos os projetos que dependem de projectName
for (const [name, project] of Object.entries(this.graph.nodes)) {
const deps = project.data.dependencies || [];
if (deps.some(dep => dep.target === projectName)) {
dependents.add(name);
// Recursivo: encontra dependents dos dependents
const transitive = this.findDependents(name);
transitive.forEach(dep => dependents.add(dep));
}
}
return dependents;
}
estimateBuildTime(projects: Set<string>): number {
let totalTime = 0;
// Calcula tempo estimado baseado em builds anteriores
for (const project of projects) {
const avgTime = this.getAverageBuildTime(project);
totalTime += avgTime;
}
return totalTime;
}
private getAverageBuildTime(project: string): number {
// Em produção, busca de analytics/histórico
const buildTimes: Record<string, number> = {
'web': 300, // 5 min
'mobile': 480, // 8 min
'admin': 240, // 4 min
'api': 180, // 3 min
'ui': 60, // 1 min
'utils': 30 // 30 sec
};
return buildTimes[project] || 120;
}
generateBuildPlan(changedFiles: string[]): {
projects: string[];
estimatedTime: number;
canParallelize: string[][];
} {
const affected = this.findAffectedProjects(changedFiles);
// Determina quais podem rodar em paralelo
const parallelGroups = this.groupParallelBuilds(Array.from(affected));
return {
projects: Array.from(affected),
estimatedTime: this.estimateBuildTime(affected),
canParallelize: parallelGroups
};
}
private groupParallelBuilds(projects: string[]): string[][] {
// Agrupa projetos que podem ser buildados em paralelo
// Projetos no mesmo grupo não têm dependências entre si
const groups: string[][] = [];
const processed = new Set<string>();
for (const project of projects) {
if (processed.has(project)) continue;
const group = [project];
processed.add(project);
// Encontra projetos que podem rodar junto
for (const other of projects) {
if (processed.has(other)) continue;
const hasSharedDeps = this.haveSharedDependencies(project, other);
if (!hasSharedDeps) {
group.push(other);
processed.add(other);
}
}
groups.push(group);
}
return groups;
}
private haveSharedDependencies(p1: string, p2: string): boolean {
const deps1 = this.graph.nodes[p1]?.data.dependencies || [];
const deps2 = this.graph.nodes[p2]?.data.dependencies || [];
return deps1.some(d1 =>
deps2.some(d2 => d1.target === d2.target)
);
}
private findProjectByFile(file: string): string | null {
for (const [name, project] of Object.entries(this.graph.nodes)) {
const root = project.data.root;
if (file.startsWith(root)) {
return name;
}
}
return null;
}
}
// Uso em CI/CD
const analyzer = new DependencyAnalyzer(projectGraph);
const changedFiles = getChangedFilesSinceLast Commit();
const buildPlan = analyzer.generateBuildPlan(changedFiles);
console.log('📊 Build Plan:');
console.log(` Affected projects: ${buildPlan.projects.length}`);
console.log(` Estimated time: ${Math.ceil(buildPlan.estimatedTime / 60)} minutes`);
console.log(` Parallel groups: ${buildPlan.canParallelize.length}`);
// Executa builds em paralelo
for (const group of buildPlan.canParallelize) {
await Promise.all(
group.map(project => buildPackage(project))
);
}
Quando Usar Monorepos?
Monorepos não são para todos os casos. Use quando:
✅ Múltiplos projetos relacionados: Frontend + Backend + Mobile sharing code
✅ Frequente sharing de código: Components, utils, types
✅ Refatoração cross-project: Mudanças que afetam múltiplos projetos
✅ CI/CD complexo: Precisa orquestrar builds de múltiplos projetos
❌ Evite quando:
- Projeto único simples
- Times completamente independentes
- Projetos sem código compartilhado
- Infraestrutura de CI limitada
Conclusão: O Futuro do Gerenciamento de Código
Monorepos com Nx ou Turborepo tornaram-se ferramenta essencial para projetos modernos. A capacidade de escalar sem friction, combinar com builds inteligentes e cache distribuído, e ter refatoração atômica fazem deles escolha óbvia para 2025.
Se você está interessado em outras tendências de arquitetura moderna, veja TypeScript em 2025: Dominando o Mercado, onde exploramos como Type Safety se integra perfeitamente com monorepos.
Bora pra cima! 🦅
📚 Quer Aprofundar Seus Conhecimentos em JavaScript?
Este artigo cobriu monorepos e arquitetura avançada, mas há muito mais para explorar no mundo do desenvolvimento moderno.
Desenvolvedores que investem em conhecimento sólido e estruturado tendem a ter mais oportunidades no mercado.
Material de Estudo Completo
Se você quer dominar JavaScript do básico ao avançado, preparei um guia completo:
Opções de investimento:
- R$9,90 (pagamento único)
💡 Material atualizado com as melhores práticas do mercado

