Arquitetura Monorepo com Turborepo: Escalabilidade e Organização

Como estruturei meu portfolio usando Turborepo para gerenciar múltiplos pacotes com eficiência e type safety

M
Mateus Salgueiro
4 min read
#turborepo#monorepo#arquitetura#typescript#devops

A escolha de uma arquitetura monorepo para este portfolio pode parecer "overkill" à primeira vista, mas os benefícios em termos de organização, reutilização de código e escalabilidade justificam completamente a decisão.

Por que Monorepo?

Vantagens Tradicionais

  1. Código compartilhado: Componentes, utilidades e tipos em um só lugar
  2. Refatoração atômica: Mudanças em múltiplos pacotes em um único commit
  3. Dependências sincronizadas: Versões consistentes em todo o projeto
  4. CI/CD simplificado: Um pipeline para todo o ecosistema

Benefícios Específicos do Projeto

portfolio/
├── apps/
│   ├── web/          # Aplicação Next.js principal
│   └── admin/        # (futuro) Painel administrativo
├── packages/
│   ├── api/          # tRPC routers e lógica de negócio
│   ├── db/           # Prisma schema e queries
│   ├── ui/           # (futuro) Biblioteca de componentes
│   └── config/       # ESLint, TypeScript configs

Turborepo: O Motor do Monorepo

Configuração Base

// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "globalDependencies": ["**/.env.*local"],
  "globalEnv": ["NODE_ENV"],
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".next/**"]
    },
    "dev": {
      "cache": false,
      "persistent": true
    },
    "db:generate": {
      "cache": false
    }
  }
}

Cache Inteligente

Turborepo cacheia builds baseado em:

  • Conteúdo dos arquivos (content hashing)
  • Dependências do pacote
  • Variáveis de ambiente

Resultado: Builds 10x mais rápidos em desenvolvimento!

Estrutura de Pacotes

Package @portfolio/api

Centraliza toda lógica de API com tRPC:

// packages/api/src/index.ts
export const appRouter = router({
  blog: blogRouter,
  contact: contactRouter,
  analytics: analyticsRouter,
})

export type AppRouter = typeof appRouter

Package @portfolio/db

Gerencia o schema Prisma e queries:

// packages/db/prisma/schema.prisma
model Article {
  id          String   @id @default(uuid())
  slug        String   @unique
  title       String
  content     String
  publishedAt DateTime @default(now())
}

Type Safety End-to-End

A melhor parte? Type safety completo do banco ao frontend:

// No frontend (apps/web)
import { trpc } from '@/lib/trpc'

function BlogPost() {
  // Types inferidos automaticamente!
  const { data } = trpc.blog.getPost.useQuery({ slug: 'hello-world' })
  return <div>{data?.title}</div>
}

Scripts Otimizados

Package.json Root

{
  "scripts": {
    "dev": "turbo dev",
    "build": "turbo build",
    "dev:web": "turbo dev --filter=web",
    "build:api": "turbo build --filter=@portfolio/api"
  }
}

Desenvolvimento Paralelo

# Inicia todos os apps em desenvolvimento
npm run dev

# Apenas a web app
npm run dev:web

# Build apenas pacotes modificados
npm run build -- --filter=[HEAD^1]

Workspace Protocol

NPM Workspaces permite referências locais:

// apps/web/package.json
{
  "dependencies": {
    "@portfolio/api": "*",
    "@portfolio/db": "*"
  }
}

CI/CD com GitHub Actions

name: CI

on:
  push:
    branches: [main]
  pull_request:

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

      - name: Setup Node
        uses: actions/setup-node@v3

      - name: Install deps
        run: npm ci

      - name: Build
        run: npm run build

      # Turborepo cacheia resultados
      - name: Test
        run: npm run test

Desafios e Soluções

1. Hot Module Replacement (HMR)

Problema: Mudanças em pacotes não triggeram HMR na app.

Solução: Usar tsdown com watch mode:

// packages/api/package.json
{
  "scripts": {
    "dev": "tsdown src/index.ts --watch"
  }
}

2. Tipos não Atualizando

Problema: TypeScript não reconhece novos tipos após build.

Solução:

# Rebuild do pacote específico
npm run build -- --filter=@portfolio/api

# Restart TS Server no VSCode
Ctrl+Shift+P → "TypeScript: Restart TS Server"

3. Prisma Generate

Problema: Cliente Prisma precisa ser regenerado após mudanças no schema.

Solução: Pipeline dependency:

{
  "pipeline": {
    "db:generate": {
      "cache": false
    },
    "build": {
      "dependsOn": ["db:generate", "^build"]
    }
  }
}

Ferramentas Complementares

Changesets

Para versionamento de pacotes:

npx changeset init
npx changeset add
npx changeset version
npx changeset publish

Syncpack

Mantém versões de dependências sincronizadas:

npx syncpack list-mismatches
npx syncpack fix-mismatches

Métricas de Performance

Com Turborepo, consegui:

  • 70% de redução no tempo de build
  • 85% cache hit rate em desenvolvimento
  • Zero duplicação de código comum
  • 100% type safety entre pacotes

Melhores Práticas

1. Boundaries Claros

Cada pacote tem responsabilidade única:

  • api: Lógica de negócio
  • db: Persistência
  • ui: Apresentação

2. Semantic Versioning

Mesmo interno, versione pacotes:

{
  "version": "1.0.0",
  "exports": {
    ".": "./dist/index.js"
  }
}

3. Tree Shaking

Configure exports corretos:

{
  "sideEffects": false,
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.mjs",
      "require": "./dist/index.js"
    }
  }
}

Futuras Expansões

A arquitetura está pronta para:

  • [ ] Micro-frontends
  • [ ] API pública versionada
  • [ ] SDK JavaScript
  • [ ] CLI tools
  • [ ] Mobile app com React Native

Conclusão

Monorepo com Turborepo transformou a maneira como desenvolvo. A organização, performance e escalabilidade compensam a complexidade inicial. Para projetos que crescem, é um investimento que vale a pena.


"A good architecture allows major decisions to be deferred." - Robert C. Martin

Artigos Relacionados

Comentários

0 comentários

Faça login para comentar

Compartilhe suas opiniões e participe da discussão