Como configurar monorepo com Turborepo para projetos frontend

1. Introdução ao Monorepo e Turborepo

Um monorepo é uma estratégia de gerenciamento de código onde múltiplos projetos são armazenados em um único repositório. Para projetos frontend, essa abordagem oferece vantagens significativas: compartilhamento de código entre aplicações, padronização de configurações e visibilidade centralizada de dependências.

Turborepo é uma ferramenta moderna de build system para monorepos JavaScript/TypeScript que se destaca por três características principais:

  • Cache inteligente: evita rebuilds desnecessários, reutilizando resultados anteriores
  • Build paralelo: executa tarefas simultaneamente quando não há dependências entre pacotes
  • Simplicidade: configuração mínima comparada a alternativas como Nx ou Lerna

Enquanto Yarn Workspaces ou pnpm Workspaces gerenciam apenas a instalação de dependências entre pacotes, o Turborepo adiciona orquestração de tarefas com cache e paralelismo.

2. Pré-requisitos e configuração inicial

Antes de começar, certifique-se de ter instalado:

  • Node.js (versão 18 ou superior)
  • pnpm (recomendado) ou yarn
  • Git

Para iniciar um monorepo com Turborepo, execute:

npx create-turbo@latest meu-monorepo-frontend
cd meu-monorepo-frontend

O comando create-turbo gera uma estrutura inicial com dois apps (web e docs) e alguns packages compartilhados. Se preferir começar do zero, crie a estrutura manualmente:

mkdir meu-monorepo-frontend
cd meu-monorepo-frontend
pnpm init
pnpm add -D turbo

3. Estrutura de pacotes e workspaces

A estrutura recomendada para projetos frontend segue este padrão:

meu-monorepo-frontend/
├── apps/
│   ├── web/          # Aplicação Next.js
│   ├── admin/        # Dashboard React
│   └── mobile/       # React Native (opcional)
├── packages/
│   ├── ui/           # Componentes compartilhados
│   ├── utils/        # Funções utilitárias
│   ├── eslint-config # Configuração ESLint
│   └── tsconfig/     # Configuração TypeScript base
├── package.json
├── turbo.json
└── pnpm-workspace.yaml

Configure o workspace no pnpm-workspace.yaml:

packages:
  - "apps/*"
  - "packages/*"

No package.json raiz, adicione:

{
  "private": true,
  "scripts": {
    "dev": "turbo run dev",
    "build": "turbo run build",
    "lint": "turbo run lint",
    "test": "turbo run test"
  },
  "devDependencies": {
    "turbo": "^2.0.0"
  }
}

4. Configuração do Turborepo para build e cache

O coração da configuração está no turbo.json. Defina os pipelines de tarefas:

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

Para ativar cache remoto (útil em times), configure com Vercel:

npx turbo login
npx turbo link

Comandos úteis para o dia a dia:

# Build de todos os pacotes
turbo run build

# Build apenas do app web
turbo run build --filter=web

# Desenvolvimento com escopo
turbo run dev --filter=web --filter=ui

5. Compartilhando código entre projetos frontend

Vamos criar um pacote de componentes UI reutilizável. Primeiro, configure packages/ui/package.json:

{
  "name": "@meu-monorepo/ui",
  "version": "0.1.0",
  "main": "./src/index.ts",
  "types": "./src/index.ts",
  "scripts": {
    "build": "tsc",
    "dev": "tsc --watch"
  },
  "devDependencies": {
    "typescript": "^5.0.0"
  }
}

Crie um componente de botão em packages/ui/src/Button.tsx:

import React from 'react';

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

export const Button: React.FC<ButtonProps> = ({
  children,
  variant = 'primary',
  onClick
}) => {
  const baseStyles = 'px-4 py-2 rounded font-medium';
  const variantStyles = {
    primary: 'bg-blue-500 text-white hover:bg-blue-600',
    secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300'
  };

  return (
    <button
      className={`${baseStyles} ${variantStyles[variant]}`}
      onClick={onClick}
    >
      {children}
    </button>
  );
};

Configure TypeScript paths no tsconfig.json raiz:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@meu-monorepo/ui": ["packages/ui/src"],
      "@meu-monorepo/utils": ["packages/utils/src"]
    }
  }
}

Agora, no app Next.js (apps/web), adicione a dependência:

{
  "dependencies": {
    "@meu-monorepo/ui": "workspace:*"
  }
}

E use o componente:

import { Button } from '@meu-monorepo/ui';

export default function Home() {
  return (
    <div>
      <Button variant="primary" onClick={() => alert('Clicou!')}>
        Meu Botão Compartilhado
      </Button>
    </div>
  );
}

6. Gerenciamento de dependências e versionamento

Para instalar dependências globais (usadas por todos os pacotes):

pnpm add -D typescript -w

Para dependências específicas de um pacote:

pnpm add react --filter=web

Para versionamento, recomendo usar Changesets:

pnpm add -D @changesets/cli -w
npx changeset init

Configure o fluxo de versionamento:

# Criar changeset
npx changeset

# Versionar pacotes
npx changeset version

# Publicar
npx changeset publish

Para evitar conflitos de versão, mantenha dependências como React, TypeScript e ESLint na raiz do monorepo:

{
  "devDependencies": {
    "typescript": "^5.3.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "pnpm": {
    "overrides": {
      "react": "^18.2.0"
    }
  }
}

7. Integração contínua e boas práticas

Configure GitHub Actions para CI eficiente com Turborepo:

# .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
      - uses: pnpm/action-setup@v2
        with:
          version: 8
      - uses: actions/setup-node@v4
        with:
          node-version: 18
          cache: 'pnpm'
      - run: pnpm install
      - run: pnpm turbo build --filter=[main]
      - run: pnpm turbo lint --filter=[main]
      - run: pnpm turbo test --filter=[main]

Use --since para executar apenas tarefas afetadas por mudanças:

turbo run build --since=main
turbo run test --filter=./packages/ui

Dicas de performance:

  • Configure .turbo no .gitignore
  • Use --concurrency para controlar paralelismo
  • Defina cache: false para tarefas que não devem ser cacheadas (como dev)

8. Próximos passos e considerações finais

Migrar projetos existentes para um monorepo com Turborepo pode ser feito gradualmente:

  1. Comece com um projeto pequeno como piloto
  2. Extraia componentes comuns para packages
  3. Adicione novos projetos ao workspace

Monitore o tamanho do repositório com ferramentas como turborepo-remote-cache e evite incluir node_modules no cache do Git.

O Turborepo é uma excelente escolha para equipes frontend que buscam produtividade sem complexidade excessiva. Sua integração com Vercel, o cache inteligente e a simplicidade de configuração o tornam ideal para projetos de médio a grande porte.


Referências