2

Monorepos com Turborepo: Estrutura, Cache e Deploy

O problema que monorepo resolve (e o que ele cria)

Dois repositórios compartilham tipos TypeScript. Alguém atualiza a interface User no repo A e esquece de propagar para o repo B. O deploy do repo B quebra às 17h de sexta. Esse cenário se repete com validações Zod, componentes de UI, configurações de ESLint e funções utilitárias. Monorepo elimina essa classe inteira de bugs: código compartilhado vive num lugar só, e qualquer mudança é visível para todos os consumidores no mesmo commit.

O custo? Builds ficam lentos, CI fica caro e deploys precisam de orquestração. Turborepo existe para atacar exatamente esses três problemas.

Estrutura de pastas que escala

A convenção do Turborepo separa apps/ (coisas que fazem deploy) de packages/ (coisas que são importadas). Parece simples, mas a decisão de o que vira pacote e o que fica dentro de uma app define a manutenibilidade do repositório inteiro.

# Estrutura base de um monorepo com Turborepo
monorepo/
├── apps/
│   ├── web/              # Next.js, faz deploy na Vercel
│   │   ├── package.json
│   │   └── next.config.js
│   └── api/              # Express/Fastify, faz deploy no Fly.io
│       ├── package.json
│       └── src/
├── packages/
│   ├── ui/               # Componentes React compartilhados
│   │   ├── package.json
│   │   └── src/
│   ├── shared-types/     # Tipos TypeScript, zero runtime
│   │   ├── package.json
│   │   └── src/
│   ├── validation/       # Schemas Zod usados por web e api
│   │   ├── package.json
│   │   └── src/
│   └── eslint-config/    # Configuração ESLint centralizada
│       └── package.json
├── turbo.json
├── package.json          # Workspace root
└── pnpm-workspace.yaml

O pnpm-workspace.yaml declara onde estão os pacotes:

# pnpm-workspace.yaml
# Turborepo funciona com npm, yarn e pnpm.
# pnpm é a escolha mais comum porque workspaces
# são cidadãos de primeira classe e o hoisting
# é mais previsível com node_modules isolados.
packages:
  - "apps/*"
  - "packages/*"

O package.json raiz não tem dependências de produção. Ele serve como ponto de entrada para scripts e define o gerenciador de pacotes:

{
  "name": "monorepo",
  "private": true,
  "scripts": {
    "build": "turbo run build",
    "dev": "turbo run dev",
    "lint": "turbo run lint",
    "test": "turbo run test",
    "clean": "turbo run clean"
  },
  "devDependencies": {
    "turbo": "^2.5.0"
  },
  "packageManager": "pnpm@9.15.0"
}

Configuração do pipeline no turbo.json

O turbo.json é onde você declara o grafo de dependências entre tasks. Turborepo lê isso para saber o que pode rodar em paralelo e o que precisa esperar.

{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": [
    "**/.env.*local"
  ],
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["src/**", "tsconfig.json", "package.json"],
      "outputs": ["dist/**", ".next/**"],
      "env": ["NODE_ENV", "DATABASE_URL"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "lint": {
      "dependsOn": ["^build"],
      "inputs": ["src/**", ".eslintrc.*", "tsconfig.json"]
    },
    "test": {
      "dependsOn": ["^build"],
      "inputs": ["src/**", "tests/**", "vitest.config.*"]
    },
    "clean": {
      "cache": false
    }
  }
}

Três detalhes que passam despercebidos:

  1. O ^build no dependsOn significa "build dos pacotes que eu importo, não o meu próprio build". Sem o ^, Turborepo tentaria rodar o build do próprio pacote como dependência de si mesmo.

  2. inputs restringe quais arquivos invalidam o cache. Se você não declara inputs, qualquer mudança em qualquer arquivo do pacote invalida tudo. Declarar inputs é a diferença entre cache hit de 95% e cache hit de 40%.

  3. env lista variáveis de ambiente que afetam o output. Se DATABASE_URL muda, o build precisa rodar de novo. Esquecer uma variável aqui causa bugs silenciosos: o cache serve um build antigo com a URL antiga.


Leia o artigo completo em https://www.vivodecodigo.com.br/infra/monorepos-turborepo-estrutura-cache-deploy

Carregando publicação patrocinada...
1