Padrões de Design Monorepo - Desenvolvimento Escalável com Turborepo, pnpm e Nx

2025.12.02

O que é Monorepo

Monorepo é um padrão de arquitetura que gerencia múltiplos projetos ou pacotes em um único repositório. É adotado por grandes empresas como Google, Meta e Microsoft.

flowchart TB
    subgraph Polyrepo["Polyrepo"]
        RA["repo-a<br/>pkg.json"] --> NPMA["npm"]
        RB["repo-b<br/>pkg.json"] --> NPMB["npm"]
        RC["repo-c<br/>pkg.json"] --> NPMC["npm"]
        RD["repo-d<br/>pkg.json"] --> NPMD["npm"]
    end

    subgraph Monorepo["Monorepo - single-repo"]
        UI["packages/<br/>ui"]
        Utils["packages/<br/>utils"]
        Web["apps/<br/>web"]
        Shared["Dependências compartilhadas"]
        UI --> Shared
        Utils --> Shared
        Web --> Shared
    end

Vantagens e Desvantagens do Monorepo

Vantagens

flowchart TB
    subgraph Benefits["Vantagens do Monorepo"]
        subgraph Share["1. Facilidade de Compartilhamento de Código"]
            Pkg["packages/shared"] --> App["apps/web"]
            Note1["Importação imediata<br/>Sem necessidade de gerenciamento de versão"]
        end

        subgraph Atomic["2. Commits Atômicos"]
            Note2["Mudanças em múltiplos pacotes em um único commit<br/>Lançamento simultâneo de breaking changes e adaptações"]
        end

        subgraph Unified["3. Toolchain Unificado"]
            Note3["Gerenciamento centralizado de ESLint, TypeScript, configurações de teste"]
        end

        subgraph Visibility["4. Visualização de Dependências"]
            Note4["Dependências entre pacotes claramente visíveis"]
        end
    end

Desvantagens e Soluções

DesvantagemSolução
Aumento do tamanho do repositórioSparse checkout, shallow clone
Aumento do tempo de execução do CIBuild incremental, execução paralela, cache remoto
Complexidade no gerenciamento de permissõesCODEOWNERS, configuração de permissões por diretório
Aumento de conflitosDivisão adequada de pacotes, responsabilidades claras

Padrões de Estrutura de Diretórios

monorepo/
├── apps/                      # Aplicações
│   ├── web/                   # Frontend
│   │   ├── src/
│   │   ├── package.json
│   │   └── tsconfig.json
│   ├── api/                   # Backend
│   │   ├── src/
│   │   ├── package.json
│   │   └── tsconfig.json
│   └── mobile/                # Aplicativo móvel
│       └── ...
├── packages/                  # Pacotes compartilhados
│   ├── ui/                    # Componentes UI
│   │   ├── src/
│   │   │   ├── Button/
│   │   │   ├── Modal/
│   │   │   └── index.ts
│   │   └── package.json
│   ├── utils/                 # Funções utilitárias
│   │   └── ...
│   ├── config/                # Configurações compartilhadas
│   │   ├── eslint/
│   │   ├── typescript/
│   │   └── tailwind/
│   └── types/                 # Definições de tipos compartilhadas
│       └── ...
├── tools/                     # Ferramentas de desenvolvimento/scripts
│   ├── scripts/
│   └── generators/
├── package.json               # package.json raiz
├── pnpm-workspace.yaml        # Configuração do workspace
├── turbo.json                 # Configuração do Turborepo
└── tsconfig.base.json         # Configuração base do TypeScript

Configuração do pnpm Workspaces

# pnpm-workspace.yaml
packages:
  - "apps/*"
  - "packages/*"
  - "tools/*"
// package.json raiz
{
  "name": "monorepo",
  "private": true,
  "scripts": {
    "dev": "turbo run dev",
    "build": "turbo run build",
    "test": "turbo run test",
    "lint": "turbo run lint",
    "clean": "turbo run clean && rm -rf node_modules"
  },
  "devDependencies": {
    "turbo": "^2.0.0",
    "typescript": "^5.4.0"
  },
  "packageManager": "pnpm@9.0.0"
}

Referência a Pacotes Internos

// apps/web/package.json
{
  "name": "@monorepo/web",
  "dependencies": {
    "@monorepo/ui": "workspace:*",
    "@monorepo/utils": "workspace:*",
    "react": "^19.0.0"
  }
}
// apps/web/src/App.tsx
import { Button, Modal } from '@monorepo/ui';
import { formatDate, debounce } from '@monorepo/utils';

export function App() {
  return (
    <div>
      <Button onClick={() => console.log(formatDate(new Date()))}>
        Clique
      </Button>
    </div>
  );
}

Otimização de Build com Turborepo

// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": ["**/.env.*local"],
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["$TURBO_DEFAULT$", ".env*"],
      "outputs": ["dist/**", ".next/**", "!.next/cache/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "test": {
      "dependsOn": ["build"],
      "inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"]
    },
    "lint": {
      "dependsOn": ["^build"],
      "outputs": []
    },
    "typecheck": {
      "dependsOn": ["^build"],
      "outputs": []
    }
  }
}

Visualização de Dependências de Tarefas

flowchart TB
    subgraph TaskGraph["Grafo de Tarefas Turborepo - turbo run build"]
        Types["@repo/types<br/>build"] --> Utils["@repo/utils<br/>build"]
        Utils --> UI["@repo/ui<br/>build"]
        Utils --> API["@repo/api<br/>build"]
        Utils --> Web["@repo/web<br/>build"]
    end

    Note1["^build: Build das dependências primeiro"]
    Note2["Execução paralela: Tarefas sem dependências executam simultaneamente"]

Configuração de Cache Remoto

# Vercel Remote Cache
npx turbo login
npx turbo link

# Cache auto-hospedado (ducktape)
# turbo.json
{
  "remoteCache": {
    "signature": true
  }
}
sequenceDiagram
    participant DevA as Desenvolvedor A<br/>(feature-1)
    participant Cache as Remote Cache
    participant DevB as Desenvolvedor B<br/>(feature-2)

    DevA->>Cache: build @repo/ui
    Cache->>Cache: hash: abc123<br/>salvar artifacts

    Note over DevA,DevB: Tempo passa

    DevB->>Cache: build @repo/ui
    Cache-->>DevB: Cache hit!<br/>Build ignorado

    Note over DevA,DevB: Build com mesma entrada completa instantaneamente (segundos)

Comparação com Nx

AspectoTurborepoNx
Curva de aprendizadoBaixaMédia~Alta
Complexidade de configuraçãoSimplesRico em recursos
GeneratorsNão possuiAbundantes
PluginsLimitadosAbundantes
CacheIntegração VercelNx Cloud
Análise de dependênciasBásicaDetalhada
Integração com IDEBásicaExtensão VSCode disponível

Critérios de escolha:

  • Prioridade na simplicidade → Turborepo
  • Recursos enterprise → Nx
  • Uso do Vercel → Turborepo
  • Uso do Angular → Nx

Pacotes de Configuração Compartilhada

// packages/config/eslint/index.js
module.exports = {
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'prettier',
  ],
  parser: '@typescript-eslint/parser',
  plugins: ['@typescript-eslint'],
  rules: {
    '@typescript-eslint/no-unused-vars': 'error',
    '@typescript-eslint/no-explicit-any': 'warn',
  },
};

// packages/config/eslint/react.js
module.exports = {
  extends: [
    './index.js',
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
  ],
  settings: {
    react: { version: 'detect' },
  },
};
// packages/config/typescript/base.json
{
  "$schema": "https://json.schemastore.org/tsconfig",
  "compilerOptions": {
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "bundler",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "declaration": true,
    "declarationMap": true
  }
}

// packages/config/typescript/react.json
{
  "extends": "./base.json",
  "compilerOptions": {
    "jsx": "react-jsx",
    "lib": ["DOM", "DOM.Iterable", "ES2022"]
  }
}

Estratégia de CI/CD

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

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2  # Para detecção de diferenças

      - uses: pnpm/action-setup@v3
        with:
          version: 9

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Build
        run: pnpm turbo run build --filter="...[HEAD^1]"
        env:
          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
          TURBO_TEAM: ${{ vars.TURBO_TEAM }}

      - name: Test
        run: pnpm turbo run test --filter="...[HEAD^1]"

      - name: Lint
        run: pnpm turbo run lint --filter="...[HEAD^1]"

Sintaxe de Filtragem

# Build apenas dos pacotes alterados
turbo run build --filter="...[HEAD^1]"

# Pacote específico e suas dependências
turbo run build --filter="@repo/web..."

# Pacote específico e seus dependentes
turbo run build --filter="...@repo/ui"

# Apenas sob diretório específico
turbo run build --filter="./apps/*"

Melhores Práticas

  1. Responsabilidade clara dos pacotes: Princípio de uma responsabilidade por pacote
  2. Evitar dependências circulares: O grafo de dependências deve sempre ser um DAG (Grafo Acíclico Direcionado)
  3. Estratégia de versionamento: Gerenciamento de versão unificado com Changesets, etc.
  4. Manutenção da documentação: README em cada pacote
  5. Granularidade adequada: Divisão de pacotes nem muito fina, nem muito grande
← Voltar para a lista