Guia Completo: Estruturando Monorepos com Turbopack
Aprenda a criar e gerenciar monorepos modernos usando Turbopack, o successor do Webpack criado pela Vercel. Guia prático com exemplos reais de configuração, otimizações e melhores práticas para escalar suas aplicações.
Introdução
Monorepos se tornaram o padrão de arquitetura para empresas que gerenciam múltiplos projetos relacionados. Com o Turbopack e Turborepo, a Vercel oferece uma solução completa para construir, gerenciar e escalar aplicações modernas em um único repositório.
Neste guia, você aprenderá desde os conceitos fundamentais até técnicas avançadas para estruturar e otimizar seu monorepo usando as ferramentas mais modernas do ecossistema JavaScript.
O Que é um Monorepo?
Um monorepo (monolithic repository) é uma estratégia de organização de código onde múltiplos projetos relacionados vivem em um único repositório Git, ao invés de estarem espalhados em repositórios separados (polyrepo).
Vantagens do Monorepo
1. Compartilhamento de Código Simplificado
- Pacotes compartilhados atualizados instantaneamente
- Sem necessidade de publicar/instalar via npm
- Refatorações atômicas em múltiplos projetos
2. Tooling Unificado
- Configuração única de linters, formatters e testes
- Dependências compartilhadas reduzem duplicação
- CI/CD simplificado
3. Visibilidade Total
- Todos os projetos acessíveis em um lugar
- Code review facilitado entre equipes
- Busca global no código
4. Atomic Commits
- Mudanças em múltiplos projetos em um único commit
- Histórico completo de dependências
- Rollbacks coordenados
Desvantagens (e Como Mitigá-las)
- Performance do Git: Mitigado com sparse checkout e shallow clones
- CI/CD Lento: Resolvido com builds incrementais (Turborepo)
- Complexidade Inicial: Compensado por scaffolding e automação
Turbopack vs Turborepo: Entendendo as Diferenças
Antes de começar, é importante diferenciar duas ferramentas da Vercel:
Turbopack 🔥
- Bundler de aplicações (successor do Webpack)
- Focado em velocidade de build e hot reload
- Integrado ao Next.js 13+
- Escrito em Rust para performance máxima
- Usa como:
next dev --turbo
Turborepo ⚡
- Build system para monorepos
- Gerencia tasks (build, test, lint) entre pacotes
- Cache inteligente e builds incrementais
- Execução paralela de tarefas
- Orquestra o monorepo inteiro
TL;DR: Turborepo orquestra o monorepo, Turbopack acelera cada aplicação Next.js dentro dele.
Arquitetura de um Monorepo Moderno
Vamos estruturar um monorepo real com múltiplas aplicações e pacotes compartilhados:
my-monorepo/
├── apps/
│ ├── web/ # App principal (Next.js)
│ ├── admin/ # Painel administrativo
│ ├── docs/ # Documentação (Nextra)
│ └── mobile/ # React Native (opcional)
├── packages/
│ ├── ui/ # Componentes compartilhados
│ ├── config/ # Configurações (ESLint, TS)
│ ├── utils/ # Funções utilitárias
│ ├── api-client/ # Cliente HTTP compartilhado
│ └── database/ # Schemas e models
├── tooling/
│ ├── eslint-config/ # Configuração ESLint
│ └── typescript-config/ # TSConfig base
├── package.json
├── pnpm-workspace.yaml
├── turbo.json
└── tsconfig.json
Setup Inicial: Criando o Monorepo
Passo 1: Inicializando com Turborepo
# Criar novo monorepo com template oficial
npx create-turbo@latest my-monorepo
# Ou com pnpm (recomendado)
pnpm dlx create-turbo@latest my-monorepo
# Navegar para o diretório
cd my-monorepo
O comando acima cria uma estrutura inicial com:
- 2 apps Next.js de exemplo
- Pacote UI compartilhado
- Configuração Turborepo pronta
Passo 2: Configurando Workspace (pnpm)
Edite pnpm-workspace.yaml:
packages:
- "apps/*"
- "packages/*"
- "tooling/*"
Este arquivo define quais diretórios contêm pacotes do workspace.
Passo 3: Configuração Global (package.json)
{
"name": "my-monorepo",
"version": "0.0.0",
"private": true,
"scripts": {
"dev": "turbo run dev",
"build": "turbo run build",
"test": "turbo run test",
"lint": "turbo run lint",
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
"clean": "turbo run clean && rm -rf node_modules"
},
"devDependencies": {
"prettier": "^3.2.4",
"turbo": "^1.12.4",
"typescript": "^5.3.3"
},
"engines": {
"node": ">=18.0.0",
"pnpm": ">=8.0.0"
},
"packageManager": "pnpm@8.15.0"
}
Configurando Turbo.json: O Cérebro do Monorepo
O arquivo turbo.json define pipelines de tasks e otimizações:
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": [".env"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**", "dist/**"],
"env": ["NEXT_PUBLIC_*", "DATABASE_URL"]
},
"dev": {
"cache": false,
"persistent": true
},
"lint": {
"dependsOn": ["^lint"],
"outputs": []
},
"test": {
"dependsOn": ["^build"],
"outputs": ["coverage/**"],
"inputs": ["src/**/*.ts", "src/**/*.tsx", "test/**"]
},
"clean": {
"cache": false
}
}
}
Entendendo as Configurações
dependsOn: ["^build"]
O ^ indica que todas as dependências do pacote atual devem executar build primeiro.
outputs
Arquivos gerados pela task. Turborepo usa isso para cache inteligente.
cache: false
Tasks como dev não devem ser cacheadas (são persistentes).
persistent: true
Mantém o processo rodando (para dev servers).
inputs
Define quais arquivos invalidam o cache quando modificados.
Criando Aplicações no Monorepo
App 1: Aplicação Web Principal (Next.js)
cd apps
pnpm create next-app@latest web --typescript --tailwind --app
Configure apps/web/package.json:
{
"name": "web",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "next dev --turbo",
"build": "next build",
"start": "next start",
"lint": "next lint",
"clean": "rm -rf .next"
},
"dependencies": {
"next": "^14.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"ui": "workspace:*",
"utils": "workspace:*"
},
"devDependencies": {
"@types/node": "^20.11.5",
"@types/react": "^18.2.48",
"typescript": "^5.3.3",
"typescript-config": "workspace:*"
}
}
Nota: workspace:* é a sintaxe do pnpm para referenciar pacotes locais.
App 2: Painel Admin
Repita o processo para criar apps/admin com configuração similar.
Criando Pacotes Compartilhados
1. Pacote UI (Componentes React)
mkdir -p packages/ui/src
cd packages/ui
pnpm init
packages/ui/package.json
{
"name": "ui",
"version": "0.0.1",
"main": "./src/index.tsx",
"types": "./src/index.tsx",
"exports": {
".": "./src/index.tsx",
"./button": "./src/button.tsx",
"./card": "./src/card.tsx"
},
"scripts": {
"lint": "eslint src/",
"test": "jest",
"clean": "rm -rf dist"
},
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"@types/react": "^18.2.48",
"eslint-config": "workspace:*",
"typescript": "^5.3.3",
"typescript-config": "workspace:*"
}
}
packages/ui/src/button.tsx
import React from 'react';
export interface ButtonProps {
children: React.ReactNode;
variant?: 'primary' | 'secondary';
onClick?: () => void;
}
export function Button({
children,
variant = 'primary',
onClick
}: ButtonProps) {
const baseStyles = "px-4 py-2 rounded-lg font-medium transition-colors";
const variantStyles = variant === 'primary'
? "bg-blue-600 text-white hover:bg-blue-700"
: "bg-gray-200 text-gray-800 hover:bg-gray-300";
return (
<button
className={`${baseStyles} ${variantStyles}`}
onClick={onClick}
>
{children}
</button>
);
}
packages/ui/src/index.tsx
export { Button } from './button';
export type { ButtonProps } from './button';
// Exporte outros componentes aqui
2. Pacote Utils (Funções Utilitárias)
packages/utils/package.json
{
"name": "utils",
"version": "0.0.1",
"main": "./src/index.ts",
"types": "./src/index.ts",
"scripts": {
"lint": "eslint src/",
"test": "jest",
"type-check": "tsc --noEmit"
},
"devDependencies": {
"eslint-config": "workspace:*",
"typescript": "^5.3.3",
"typescript-config": "workspace:*"
}
}
packages/utils/src/index.ts
export function formatDate(date: Date): string {
return new Intl.DateTimeFormat('pt-BR', {
day: '2-digit',
month: 'long',
year: 'numeric'
}).format(date);
}
export function debounce<T extends (...args: any[]) => any>(
func: T,
wait: number
): (...args: Parameters<T>) => void {
let timeout: NodeJS.Timeout;
return function executedFunction(...args: Parameters<T>) {
clearTimeout(timeout);
timeout = setTimeout(() => func(...args), wait);
};
}
export function slugify(text: string): string {
return text
.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/[^a-z0-9]+/g, '-')
.replace(/(^-|-$)/g, '');
}
3. Configuração TypeScript Compartilhada
tooling/typescript-config/package.json
{
"name": "typescript-config",
"version": "0.0.1",
"main": "index.js",
"files": ["base.json", "nextjs.json", "react-library.json"]
}
tooling/typescript-config/base.json
{
"$schema": "https://json.schemastore.org/tsconfig",
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2022"],
"module": "ESNext",
"moduleResolution": "Bundler",
"resolveJsonModule": true,
"allowJs": true,
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"incremental": true,
"esModuleInterop": true,
"isolatedModules": true
},
"exclude": ["node_modules"]
}
tooling/typescript-config/nextjs.json
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "./base.json",
"compilerOptions": {
"jsx": "preserve",
"lib": ["dom", "dom.iterable", "ES2022"],
"plugins": [{ "name": "next" }],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
Agora use nos apps:
apps/web/tsconfig.json
{
"extends": "typescript-config/nextjs.json",
"compilerOptions": {
"baseUrl": "."
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"]
}
Usando Turbopack no Desenvolvimento
Habilitando Turbopack no Next.js
No package.json de cada app Next.js:
{
"scripts": {
"dev": "next dev --turbo",
"build": "next build"
}
}
Performance: Webpack vs Turbopack
| Métrica | Webpack 5 | Turbopack |
|---|---|---|
| Cold Start | ~8s | ~1.2s |
| Hot Reload | ~500ms | ~50ms |
| Linguagem | JavaScript | Rust |
| Incremental | Parcial | Total |
Configuração Avançada (next.config.js)
/** @type {import('next').NextConfig} */
const nextConfig = {
// Transpile pacotes do workspace
transpilePackages: ['ui', 'utils'],
experimental: {
// Habilita Turbopack (default em next dev --turbo)
turbo: {
// Regras de transformação customizadas
rules: {
'*.svg': {
loaders: ['@svgr/webpack'],
as: '*.js',
},
},
},
},
};
module.exports = nextConfig;
Gerenciamento de Dependências
Instalando Dependências
Para um pacote específico:
# Na raiz do monorepo
pnpm add react --filter web
pnpm add -D typescript --filter ui
Para todos os pacotes:
pnpm add -D prettier -w
O flag -w (workspace root) instala na raiz.
Dependências Internas
Use workspace:* para versão flexível:
{
"dependencies": {
"ui": "workspace:*",
"utils": "workspace:^"
}
}
workspace:*: Sempre a versão mais recenteworkspace:^: Respeitando semver
Hoisting de Dependências
Edite .npmrc:
# Hoisting: mover dependências para node_modules da raiz
shamefully-hoist=true
public-hoist-pattern[]=*eslint*
public-hoist-pattern[]=*prettier*
# Isolamento por pacote
node-linker=isolated
Executando Tasks com Turborepo
Comandos Básicos
# Roda 'dev' em todos os pacotes com essa task
turbo run dev
# Roda 'build' apenas em apps/web e dependências
turbo run build --filter=web
# Roda 'test' em paralelo (4 workers)
turbo run test --concurrency=4
# Força rebuild ignorando cache
turbo run build --force
# Visualiza dependências
turbo run build --graph=graph.html
Filtros Avançados
# Apenas packages/ui
turbo run build --filter=ui
# web e todas as dependências
turbo run build --filter=web...
# Todos os pacotes que dependem de ui
turbo run test --filter=...ui
# Apenas pacotes modificados
turbo run build --filter=[HEAD^]
Pipeline com Dependências
{
"pipeline": {
"build": {
// Primeiro builda dependências, depois o pacote atual
"dependsOn": ["^build"],
"outputs": ["dist/**"]
},
"test": {
// Testa após build do próprio pacote
"dependsOn": ["build"],
"outputs": ["coverage/**"]
},
"deploy": {
// Deploy após build e test
"dependsOn": ["build", "test"]
}
}
}
Caching e Otimização
Cache Local
Turborepo cacheia outputs de tasks automaticamente:
# Primeira execução: ~30s
turbo run build
# Segunda execução (sem mudanças): ~100ms (FULL TURBO 🔥)
turbo run build
Remote Caching (Vercel)
Configure cache compartilhado entre CI e desenvolvedores:
# Login na Vercel
pnpm dlx turbo login
# Linkar repositório
pnpm dlx turbo link
Adicione ao turbo.json:
{
"remoteCache": {
"enabled": true,
"signature": true
}
}
Invalidação de Cache
O cache invalida quando:
- Arquivos de input mudam (baseado em hash)
- Dependências externas mudam (package.json)
- Variáveis de ambiente listadas mudam
- Outputs de dependências mudam
TypeScript: Path Aliases no Monorepo
Configure imports absolutos em cada pacote:
apps/web/tsconfig.json
{
"extends": "typescript-config/nextjs.json",
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"],
"@ui/*": ["../../packages/ui/src/*"],
"@utils/*": ["../../packages/utils/src/*"]
}
}
}
Agora use:
// Ao invés de:
import { Button } from '../../packages/ui/src/button';
// Use:
import { Button } from 'ui/button';
// ou
import { Button } from '@ui/button';
Docker: Build Otimizado de Monorepo
Dockerfile Multi-Stage
# Estágio 1: Dependências
FROM node:20-alpine AS deps
RUN corepack enable && corepack prepare pnpm@8.15.0 --activate
WORKDIR /app
# Copiar apenas arquivos de dependências
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY apps/web/package.json ./apps/web/
COPY packages/*/package.json ./packages/
# Instalar dependências
RUN pnpm install --frozen-lockfile
# Estágio 2: Builder
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Build apenas o app web
RUN pnpm turbo run build --filter=web
# Estágio 3: Runner
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/apps/web/next.config.js ./
COPY --from=builder /app/apps/web/package.json ./
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "apps/web/server.js"]
.dockerignore
node_modules
.next
.turbo
dist
coverage
.env*.local
CI/CD: GitHub Actions
Workflow Otimizado
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8
- name: Get pnpm store directory
id: pnpm-cache
run: echo "pnpm_cache_dir=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Setup pnpm cache
uses: actions/cache@v3
with:
path: ${{ steps.pnpm-cache.outputs.pnpm_cache_dir }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Lint
run: pnpm turbo run lint
- name: Type Check
run: pnpm turbo run type-check
- name: Test
run: pnpm turbo run test
- name: Build
run: pnpm turbo run build
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
Melhores Práticas
1. Estrutura de Commits
Use Conventional Commits com scopes:
git commit -m "feat(web): adiciona página de login"
git commit -m "fix(ui): corrige estilo do Button"
git commit -m "chore(deps): atualiza Next.js para 14.1"
2. Versionamento
Use Changesets para gerenciar versões:
pnpm add -Dw @changesets/cli
pnpm changeset init
Criar changeset:
pnpm changeset
Publicar:
pnpm changeset version
pnpm changeset publish
3. Code Sharing Guidelines
Quando criar um pacote:
- ✅ Código usado em 2+ apps
- ✅ Lógica independente de domínio
- ✅ Pode ser testado isoladamente
Quando NÃO criar:
- ❌ Código usado uma vez
- ❌ Lógica muito específica de um app
- ❌ Está mudando constantemente
4. Performance Tips
// turbo.json
{
"pipeline": {
"build": {
// Defina outputs precisos para melhor caching
"outputs": [
".next/**",
"!.next/cache/**"
],
// Liste apenas envs realmente usadas
"env": [
"NEXT_PUBLIC_API_URL",
"DATABASE_URL"
]
}
}
}
5. Segurança
Nunca commite .env:
.env*
!.env.example
Use env validation:
// packages/env/src/index.ts
import { z } from 'zod';
const envSchema = z.object({
DATABASE_URL: z.string().url(),
NEXT_PUBLIC_API_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
});
export const env = envSchema.parse(process.env);
Troubleshooting
Problema: "Module not found" em pacote local
Causa: TypeScript não encontra o pacote do workspace.
Solução:
// tsconfig.json
{
"compilerOptions": {
"paths": {
"ui": ["../../packages/ui/src/index.tsx"],
"ui/*": ["../../packages/ui/src/*"]
}
}
}
E em next.config.js:
module.exports = {
transpilePackages: ['ui', 'utils'],
};
Problema: Turbo cache não funciona
Causa: Outputs não definidos corretamente.
Solução:
# Limpar cache
turbo run build --force
# Debugar outputs
turbo run build --dry-run
Problema: Slow installs
Solução: Use pnpm com hoisting:
# .npmrc
shamefully-hoist=true
auto-install-peers=true
strict-peer-dependencies=false
Problema: Conflito de dependências
Causa: Versões diferentes do mesmo pacote.
Solução:
// package.json (raiz)
{
"pnpm": {
"overrides": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
}
}
Recursos Avançados
1. Code Generation com Plop
pnpm add -Dw plop
plopfile.js
module.exports = function (plop) {
plop.setGenerator('component', {
description: 'Create a new component',
prompts: [{
type: 'input',
name: 'name',
message: 'Component name:'
}],
actions: [{
type: 'add',
path: 'packages/ui/src/{{kebabCase name}}.tsx',
templateFile: 'templates/component.tsx.hbs'
}]
});
};
2. Shared ESLint Config
tooling/eslint-config/package.json
{
"name": "eslint-config",
"main": "index.js",
"dependencies": {
"@typescript-eslint/eslint-plugin": "^6.19.0",
"@typescript-eslint/parser": "^6.19.0",
"eslint-config-next": "^14.1.0",
"eslint-config-prettier": "^9.1.0"
}
}
tooling/eslint-config/index.js
module.exports = {
extends: [
"next/core-web-vitals",
"prettier"
],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 2022,
sourceType: "module"
},
rules: {
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-explicit-any": "warn"
}
};
3. Storybook no Monorepo
cd packages/ui
pnpx storybook init
packages/ui/.storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
stories: ['../src/**/*.stories.tsx'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
],
framework: {
name: '@storybook/react-vite',
options: {},
},
};
export default config;
Migração de Polyrepo para Monorepo
Estratégia de Migração
Fase 1: Preparação
- Criar estrutura do monorepo
- Mover primeiro app (menos crítico)
- Validar build e deploy
Fase 2: Migração Gradual
- Identificar código compartilhado
- Extrair para pacotes
- Migrar apps um por um
Fase 3: Otimização
- Configurar Turborepo
- Setup de remote caching
- Otimizar CI/CD
Script de Migração
#!/bin/bash
# migrate-repo.sh
# Move repo existente para apps/
REPO_URL=$1
APP_NAME=$2
# Clone
git clone $REPO_URL temp-repo
# Move para apps/
mkdir -p apps/$APP_NAME
mv temp-repo/* apps/$APP_NAME/
rm -rf temp-repo
# Atualiza package.json
cd apps/$APP_NAME
jq '.name = "'$APP_NAME'"' package.json > tmp.json
mv tmp.json package.json
# Instala dependências
cd ../..
pnpm install
echo "✅ Migrado $REPO_URL para apps/$APP_NAME"
Conclusão
Monorepos com Turbopack e Turborepo representam o estado da arte em desenvolvimento moderno de aplicações JavaScript. Os benefícios vão muito além de organização de código:
Performance: Builds 10x mais rápidos com Turbopack Produtividade: Compartilhamento instantâneo de código Escalabilidade: Arquitetura que cresce com seu negócio DevEx: Tooling unificado e workflows simplificados
Próximos Passos
- Experimentar: Crie um monorepo de teste com o template oficial
- Estudar: Analise monorepos open source (Vercel, Turborepo, Cal.com)
- Implementar: Migre gradualmente seus projetos
- Otimizar: Configure remote caching e CI/CD
Recursos Adicionais
Autor: Kaique Yamamoto Data: 28 de janeiro de 2026
Tags: #monorepo #turbopack #turborepo #nextjs #architecture #performance #typescript #pnpm #workspace
Artigos Relacionados
Event-Driven Architecture com C#, .NET e Kafka: Guia Prático
Aprenda a construir sistemas escaláveis usando Event-Driven Architecture com C#, .NET e Apache Kafka. Guia completo com exemplos práticos, integração multi-linguagem e melhores práticas de produção.
Arquitetura de Monorepo Distribuída: Baseada no Livro Migrando Monolitos para Microserviços
Monorepo distribuído une um único repositório à execução em serviços independentes. Este artigo conecta as ideias do livro Migrando Monolitos para Microserviços com práticas de monorepo, bounded contexts e evolução gradual.
Observabilidade Escalável: Zabbix, Prometheus e Grafana em Infraestruturas de Todos os Portes
Guia prático sobre como implementar monitoramento com Zabbix, Prometheus e Grafana, adaptado para infraestruturas de pequeno, médio e grande porte.