Voltar para o Blog

Monorepos com Nx e Turborepo: Como Grandes Empresas Gerenciam Projetos em 2025

Olá HaWkers, se você já trabalhou em uma empresa com múltiplos projetos relacionados - um app mobile, um dashboard admin, um site público, uma API - provavelmente já sentiu a dor de manter código duplicado, versões desalinhadas de bibliotecas compartilhadas, e deploys que parecem uma orquestra sem maestro.

Você já parou para pensar como empresas como Google, Facebook e Microsoft conseguem gerenciar centenas de projetos que compartilham código sem cair no caos? E mais importante: como você pode aplicar essas mesmas estratégias nos seus projetos, mesmo em equipes menores?

O Que São Monorepos e Por Que Importam em 2025

Monorepo é uma estratégia de organização de código onde múltiplos projetos relacionados vivem no mesmo repositório Git. Em vez de ter um repositório para cada app, você tem um único repositório contendo todos os seus projetos, compartilhando código, ferramentas e pipelines.

Em 2025, monorepos deixaram de ser uma curiosidade de gigantes da tecnologia para se tornarem mainstream. As ferramentas amadureceram, a documentação melhorou, e os benefícios se tornaram impossíveis de ignorar:

Vantagens claras:

  • Code sharing simplificado: Compartilhe bibliotecas entre projetos sem publicar em npm
  • Refatoração atômica: Mude uma API e atualize todos os consumidores no mesmo commit
  • Consistência: Mesmas versões de dependências, mesmas ferramentas, mesmos padrões
  • Visibility: Veja como mudanças afetam todos os projetos
  • CI/CD otimizado: Execute testes e builds apenas do que mudou

Desafios resolvidos em 2025:

  • Performance de ferramentas (Nx e Turborepo são 7x+ mais rápidos)
  • Complexidade de setup (templates prontos e documentação clara)
  • Escalabilidade (suporte a milhares de projetos)

Nx: A Solução Enterprise-Grade

Nx é uma ferramenta de monorepo poderosa, criada pela Nrwl, focada em escalabilidade e experiência do desenvolvedor. Em 2025, Nx se consolidou como a escolha para monorepos complexos e polyglot (múltiplas linguagens).

Características do Nx

  1. Task orchestration inteligente: Nx entende dependências entre projetos e executa tasks na ordem correta
  2. Computation caching: Resultados de builds e testes são cacheados localmente e remotamente
  3. Affected commands: Execute tasks apenas em projetos afetados por mudanças
  4. Plugins para tudo: React, Angular, Node, Go, Rust, e mais
  5. Dependency graph: Visualização interativa de como projetos se relacionam

Setup de um Monorepo Nx

# Criar novo workspace Nx
npx create-nx-workspace@latest my-workspace

# Escolha as opções:
# - Package-based monorepo ou Integrated monorepo
# - TypeScript/JavaScript
# - CI/CD provider (GitHub Actions, GitLab, etc)

cd my-workspace

# Adicionar aplicações
nx g @nx/react:app web
nx g @nx/react:app admin
nx g @nx/node:app api

# Adicionar bibliotecas compartilhadas
nx g @nx/js:lib shared-ui
nx g @nx/js:lib shared-utils
nx g @nx/js:lib data-access

Estrutura de um Monorepo Nx

my-workspace/
├── apps/
│   ├── web/               # App React do site público
│   ├── admin/             # Dashboard administrativo
│   └── api/               # Backend Node.js
├── libs/
│   ├── shared-ui/         # Componentes UI compartilhados
│   ├── shared-utils/      # Utilitários compartilhados
│   └── data-access/       # Cliente API compartilhado
├── tools/
│   └── generators/        # Geradores customizados
├── nx.json                # Configuração do Nx
├── package.json
└── tsconfig.base.json

Configuração nx.json

{
  "extends": "nx/presets/npm.json",
  "tasksRunnerOptions": {
    "default": {
      "runner": "nx/tasks-runners/default",
      "options": {
        "cacheableOperations": ["build", "test", "lint"],
        "parallel": 3
      }
    }
  },
  "targetDefaults": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["{projectRoot}/dist"]
    },
    "test": {
      "cache": true
    }
  },
  "namedInputs": {
    "default": ["{projectRoot}/**/*"],
    "production": ["!{projectRoot}/**/*.spec.ts"]
  }
}

Trabalhando com Bibliotecas Compartilhadas

// libs/shared-ui/src/lib/Button.tsx
import React from 'react';

export interface ButtonProps {
  variant?: 'primary' | 'secondary';
  onClick?: () => void;
  children: React.ReactNode;
}

export function Button({ variant = 'primary', onClick, children }: ButtonProps) {
  return (
    <button
      className={`btn btn-${variant}`}
      onClick={onClick}
    >
      {children}
    </button>
  );
}

// libs/shared-ui/src/index.ts - Barrel export
export * from './lib/Button';
export * from './lib/Input';
export * from './lib/Modal';

Usando em apps:

// apps/web/src/app/app.tsx
import { Button } from '@my-workspace/shared-ui';

export function App() {
  return (
    <div>
      <h1>Welcome to Web App</h1>
      <Button variant="primary" onClick={() => console.log('Clicked')}>
        Click Me
      </Button>
    </div>
  );
}

Data Access Layer Compartilhado

// libs/data-access/src/lib/api-client.ts
export class ApiClient {
  private baseUrl: string;

  constructor(baseUrl: string) {
    this.baseUrl = baseUrl;
  }

  async get<T>(endpoint: string): Promise<T> {
    const response = await fetch(`${this.baseUrl}${endpoint}`);
    if (!response.ok) {
      throw new Error(`API Error: ${response.statusText}`);
    }
    return response.json();
  }

  async post<T, D>(endpoint: string, data: D): Promise<T> {
    const response = await fetch(`${this.baseUrl}${endpoint}`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data),
    });

    if (!response.ok) {
      throw new Error(`API Error: ${response.statusText}`);
    }

    return response.json();
  }
}

// libs/data-access/src/lib/users-service.ts
import { ApiClient } from './api-client';

export interface User {
  id: string;
  name: string;
  email: string;
}

export class UsersService {
  constructor(private api: ApiClient) {}

  async getUsers(): Promise<User[]> {
    return this.api.get<User[]>('/users');
  }

  async getUser(id: string): Promise<User> {
    return this.api.get<User>(`/users/${id}`);
  }

  async createUser(user: Omit<User, 'id'>): Promise<User> {
    return this.api.post<User, Omit<User, 'id'>>('/users', user);
  }
}

// libs/data-access/src/index.ts
export * from './lib/api-client';
export * from './lib/users-service';

Uso nos apps:

// apps/web/src/services/index.ts
import { ApiClient, UsersService } from '@my-workspace/data-access';

const apiClient = new ApiClient(process.env.NX_API_URL || 'http://localhost:3333');
export const usersService = new UsersService(apiClient);

// apps/web/src/app/users-page.tsx
import { useEffect, useState } from 'react';
import { usersService } from '../services';
import { User } from '@my-workspace/data-access';

export function UsersPage() {
  const [users, setUsers] = useState<User[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    usersService.getUsers()
      .then(setUsers)
      .finally(() => setLoading(false));
  }, []);

  if (loading) return <div>Loading...</div>;

  return (
    <div>
      <h1>Users</h1>
      {users.map(user => (
        <div key={user.id}>
          {user.name} - {user.email}
        </div>
      ))}
    </div>
  );
}

Turborepo: Velocidade e Simplicidade

Turborepo, criado por Vercel, foca em simplicidade e performance extrema. É mais leve que Nx mas incrivelmente eficiente para monorepos JavaScript/TypeScript.

Características do Turborepo

  1. Zero config cache: Cache distribuído com zero configuração
  2. Incremental builds: Só rebuilda o que mudou
  3. Remote caching: Compartilhe cache entre toda a equipe
  4. Lean: Filosofia minimalista, aproveita ferramentas existentes
  5. Vercel integration: Integração nativa com Vercel

Setup de um Monorepo Turborepo

# Criar novo monorepo
npx create-turbo@latest

cd my-turborepo

# Estrutura gerada:
# apps/
#   web/          # Next.js app
#   docs/         # Next.js docs site
# packages/
#   ui/           # Shared UI components
#   eslint-config/
#   tsconfig/
# turbo.json
# package.json

Configuração turbo.json

{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": [".env"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "dist/**"]
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": ["coverage/**"],
      "cache": true
    },
    "lint": {
      "cache": true
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

Workspace Package.json

{
  "name": "my-turborepo",
  "private": true,
  "workspaces": ["apps/*", "packages/*"],
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
    "test": "turbo run test",
    "lint": "turbo run lint"
  },
  "devDependencies": {
    "turbo": "latest"
  }
}

Shared Package UI

// packages/ui/src/Button.tsx
import * as React from 'react';

export interface ButtonProps {
  children: React.ReactNode;
  onClick?: () => void;
  variant?: 'primary' | 'secondary';
}

export function Button({ children, onClick, variant = 'primary' }: ButtonProps) {
  return (
    <button
      onClick={onClick}
      className={`button button--${variant}`}
    >
      {children}
    </button>
  );
}

// packages/ui/package.json
{
  "name": "@repo/ui",
  "version": "0.0.0",
  "main": "./src/index.tsx",
  "types": "./src/index.tsx",
  "license": "MIT",
  "scripts": {
    "lint": "eslint src/",
    "test": "jest"
  },
  "devDependencies": {
    "@repo/eslint-config": "*",
    "@repo/typescript-config": "*",
    "@types/react": "^18.2.0",
    "react": "^18.2.0"
  },
  "peerDependencies": {
    "react": "^18.2.0"
  }
}

// packages/ui/src/index.tsx
export * from './Button';
export * from './Input';
export * from './Card';

Remote Caching com Vercel

# Conectar ao Vercel Remote Cache
npx turbo login

# Link ao projeto
npx turbo link

# Agora todos os builds são cacheados na nuvem
# Toda a equipe se beneficia do cache compartilhado
// turbo.json com remote cache
{
  "$schema": "https://turbo.build/schema.json",
  "remoteCache": {
    "signature": true
  },
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "dist/**"]
    }
  }
}

Nx vs Turborepo: Quando Usar Cada Um

Use Nx quando:

  1. Projetos polyglot: Precisa de suporte a múltiplas linguagens (TypeScript, Go, Rust, Python)
  2. Escala enterprise: Centenas de projetos, múltiplos times
  3. Customização profunda: Precisa de geradores customizados, plugins específicos
  4. Dependency graph complexo: Quer visualização e análise profunda de dependências
  5. Conformance rules: Precisa garantir padrões entre toda organização

Use Turborepo quando:

  1. JavaScript/TypeScript only: Foco total em ecossistema JS
  2. Simplicidade: Quer configuração mínima, aproveitar ferramentas existentes
  3. Vercel ecosystem: Usa Next.js e deploys na Vercel
  4. Performance pura: Prioriza velocidade absoluta de build
  5. Equipe pequena/média: Não precisa de toda complexidade do Nx
// Comparação de performance (projeto médio com 20 apps)
const benchmarks = {
  nx: {
    coldBuild: '45s',
    cachedBuild: '2.1s',
    affectedBuild: '8.3s',
    features: ['affected', 'graph', 'plugins', 'generators']
  },
  turborepo: {
    coldBuild: '38s',
    cachedBuild: '1.8s',
    affectedBuild: '7.1s',
    features: ['cache', 'pipeline', 'remote-cache']
  }
};

console.table(benchmarks);
// Ambos são extremamente rápidos, escolha baseada em features necessárias

Estratégias de Organização de Código

Boundary Rules (Nx)

// .eslintrc.json - Enforça limites entre módulos
{
  "overrides": [
    {
      "files": ["*.ts"],
      "rules": {
        "@nx/enforce-module-boundaries": [
          "error",
          {
            "allow": [],
            "depConstraints": [
              {
                "sourceTag": "type:app",
                "onlyDependOnLibsWithTags": ["type:feature", "type:ui", "type:util"]
              },
              {
                "sourceTag": "type:feature",
                "onlyDependOnLibsWithTags": ["type:ui", "type:util", "type:data-access"]
              },
              {
                "sourceTag": "type:ui",
                "onlyDependOnLibsWithTags": ["type:util"]
              }
            ]
          }
        ]
      }
    }
  ]
}

Tagging System

// libs/shared-ui/project.json
{
  "name": "shared-ui",
  "tags": ["type:ui", "scope:shared"]
}

// libs/web-feature-auth/project.json
{
  "name": "web-feature-auth",
  "tags": ["type:feature", "scope:web"]
}

// libs/data-access/project.json
{
  "name": "data-access",
  "tags": ["type:data-access", "scope:shared"]
}

Code Generators (Nx)

// tools/generators/component/index.ts
import { Tree, formatFiles, installPackagesTask } from '@nx/devkit';

export default async function (tree: Tree, schema: any) {
  const projectRoot = `libs/${schema.project}/src/lib`;

  tree.write(
    `${projectRoot}/${schema.name}.tsx`,
    `import React from 'react';

export interface ${schema.name}Props {
  children?: React.ReactNode;
}

export function ${schema.name}({ children }: ${schema.name}Props) {
  return (
    <div>
      <h1>${schema.name}</h1>
      {children}
    </div>
  );
}
`
  );

  tree.write(
    `${projectRoot}/${schema.name}.spec.tsx`,
    `import { render } from '@testing-library/react';
import { ${schema.name} } from './${schema.name}';

describe('${schema.name}', () => {
  it('should render successfully', () => {
    const { baseElement } = render(<${schema.name} />);
    expect(baseElement).toBeTruthy();
  });
});
`
  );

  await formatFiles(tree);
  return () => {
    installPackagesTask(tree);
  };
}

// Uso:
// nx g @my-workspace/tools:component MyComponent --project=shared-ui

CI/CD Otimizado para Monorepos

GitHub Actions com Nx

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  main:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v3
        with:
          node-version: 18
          cache: 'npm'

      - run: npm ci

      - uses: nrwl/nx-set-shas@v3

      - run: npx nx affected -t lint test build --parallel=3
        env:
          NX_CLOUD_ACCESS_TOKEN: ${{ secrets.NX_CLOUD_ACCESS_TOKEN }}

GitHub Actions com Turborepo

# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - uses: actions/setup-node@v3
        with:
          node-version: 18
          cache: 'npm'

      - run: npm ci

      - run: npx turbo run build test lint
        env:
          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
          TURBO_TEAM: ${{ secrets.TURBO_TEAM }}

Melhores Práticas Monorepo 2025

  1. Use workspace protocol para dependências internas
{
  "dependencies": {
    "@my-workspace/shared-ui": "workspace:*"
  }
}
  1. Versione bibliotecas compartilhadas semanticamente
nx release version --skip-publish
nx release publish
  1. Configure paths no tsconfig.base.json
{
  "compilerOptions": {
    "paths": {
      "@my-workspace/shared-ui": ["libs/shared-ui/src/index.ts"],
      "@my-workspace/*": ["libs/*/src/index.ts"]
    }
  }
}
  1. Use conventional commits para changelogs automáticos
git commit -m "feat(shared-ui): add new Button variant"
git commit -m "fix(api): resolve authentication bug"
  1. Implemente code owners
# CODEOWNERS
/libs/shared-ui/ @frontend-team
/apps/api/ @backend-team
/libs/data-access/ @full-stack-team

O Futuro dos Monorepos

Em 2025, monorepos se consolidaram como a forma preferida de organizar código em empresas de todos os tamanhos. As ferramentas estão maduras, a comunidade é forte, e os benefícios são claros.

O que vem por aí:

  1. IA-powered code generation: Geradores que entendem seu padrão e sugerem código
  2. Ainda mais performance: Ferramentas escritas em Rust/Go ficando ainda mais rápidas
  3. Cloud development environments: Workspaces rodando na nuvem com cache compartilhado
  4. Cross-language monorepos: Suporte ainda melhor para projetos polyglot
  5. Automated dependency updates: IA que atualiza dependências de forma segura

Monorepos não são silver bullet, mas para a maioria das organizações com múltiplos projetos relacionados, são a melhor escolha em 2025. A questão não é mais "se", mas "quando" fazer a transição.

Se você quer continuar aprofundando em ferramentas e práticas modernas de desenvolvimento, recomendo voltar ao início desta série: Mercado de Desenvolvimento de Software em 2025: Tendências, Salários e Skills em Alta Demanda onde você vai ter uma visão completa do mercado atual.

Bora pra cima! 🦅

📚 Quer Aprofundar Seus Conhecimentos em JavaScript?

Este artigo cobriu monorepos e arquitetura de projetos, 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)

👉 Conhecer o Guia JavaScript

💡 Material atualizado com as melhores práticas do mercado

Comentários (0)

Esse artigo ainda não possui comentários 😢. Seja o primeiro! 🚀🦅

Adicionar comentário