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
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
- Código compartilhado: Componentes, utilidades e tipos em um só lugar
- Refatoração atômica: Mudanças em múltiplos pacotes em um único commit
- Dependências sincronizadas: Versões consistentes em todo o projeto
- 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óciodb: Persistênciaui: 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
Bem-vindo ao Meu Portfolio
Conheça meu portfolio pessoal, desenvolvido com as tecnologias mais modernas do ecossistema JavaScript/TypeScript
Construindo um Sistema Solar 3D com Three.js e React
Como desenvolvi uma visualização interativa do sistema solar usando Three.js, React Three Fiber e dados reais da NASA