turborepo
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTurborepo - Monorepo Architecture Expert
Turborepo - 单体仓库(Monorepo)架构专家
Assumption: You know . This covers architectural decisions.
turbo run build前提假设:你已了解 。本指南聚焦架构决策相关内容。
turbo run buildBefore Adopting Turborepo: Strategic Assessment
采用Turborepo之前:战略评估
Ask yourself these questions BEFORE committing to monorepo:
在决定使用单体仓库前,请先问自己这些问题:
1. Team & Coordination Analysis
1. 团队与协作分析
- Team size: 1-3 engineers → Polyrepo simpler (monorepo overhead not worth it)
- Shared code percentage: <20% → Polyrepo, >50% → Monorepo compelling
- Coordination pain: Breaking changes require 3+ repos updated → Monorepo wins
- Deployment coupling: Services deploy together → Monorepo, Independently → Polyrepo
- 团队规模:1-3名工程师 → 多仓库(polyrepo)更简单(单体仓库的管理成本得不偿失)
- 共享代码占比:<20% → 多仓库,>50% → 单体仓库更具优势
- 协作痛点:破坏性变更需要更新3个以上仓库 → 单体仓库更合适
- 部署耦合性:服务需一起部署 → 单体仓库,独立部署 → 多仓库
2. Technical Complexity Assessment
2. 技术复杂度评估
- Languages: Pure JS/TS → Turborepo works, Mixed (Go/Python) → Nx or polyrepo
- Build time: <5min total across all apps → Overhead not justified yet
- Cache importance: Long builds (>2min per package) → Turborepo caching critical
- CI complexity: Simple pipeline → Polyrepo easier, Complex (affected detection) → Monorepo
- 开发语言:纯JS/TS → Turborepo适用,多语言混合(Go/Python)→ 选择Nx或多仓库
- 构建时间:所有应用总构建时间<5分钟 → 暂不需要单体仓库的额外功能
- 缓存重要性:构建时间长(每个包>2分钟)→ Turborepo缓存至关重要
- CI复杂度:简单流水线 → 多仓库更易维护,复杂流水线(变更影响检测)→ 单体仓库
3. Maintenance Cost Analysis
3. 维护成本分析
- What breaks with monorepo: Version conflicts, build order issues, cache debugging, tooling complexity
- What breaks with polyrepo: API version hell, coordination overhead, code duplication, cross-repo changes
- Break-even point: Monorepo worth it when 3+ apps share 30%+ code + frequent coordination needed
- 单体仓库的风险:版本冲突、构建顺序问题、缓存调试、工具复杂度
- 多仓库的风险:API版本混乱、协作成本高、代码重复、跨仓库变更繁琐
- 盈亏平衡点:当3个以上应用共享30%+代码且需要频繁协作时,单体仓库的价值凸显
Critical Rule: Package Tasks, Not Root Tasks
核心规则:为包定义任务,而非根目录任务
The #1 Turborepo mistake: Putting task logic in root .
package.jsonjson
// ❌ WRONG - defeats parallelization
// Root package.json
{
"scripts": {
"build": "cd apps/web && next build && cd ../api && tsc",
"lint": "eslint apps/ packages/",
"test": "vitest"
}
}
// ✅ CORRECT - parallel execution
// apps/web/package.json
{ "scripts": { "build": "next build", "lint": "eslint .", "test": "vitest" } }
// apps/api/package.json
{ "scripts": { "build": "tsc", "lint": "eslint .", "test": "vitest" } }
// Root package.json - ONLY delegates
{ "scripts": { "build": "turbo run build" } }Why it breaks: Turborepo can't parallelize sequential shell commands. Package tasks enable task graph parallelization.
Turborepo最常见错误:将任务逻辑放在根目录中。
package.jsonjson
// ❌ 错误 - 无法实现并行化
// 根目录 package.json
{
"scripts": {
"build": "cd apps/web && next build && cd ../api && tsc",
"lint": "eslint apps/ packages/",
"test": "vitest"
}
}
// ✅ 正确 - 支持并行执行
// apps/web/package.json
{ "scripts": { "build": "next build", "lint": "eslint .", "test": "vitest" } }
// apps/api/package.json
{ "scripts": { "build": "tsc", "lint": "eslint .", "test": "vitest" } }
// 根目录 package.json - 仅做任务代理
{ "scripts": { "build": "turbo run build" } }问题原因:Turborepo无法并行化执行顺序shell命令。为每个包定义任务才能实现任务图的并行化。
Decision: When to Split a Package
决策:何时拆分包
Considering splitting code into package?
│
├─ Code used by 1 app only → DON'T split yet
│ └─ Keep in app until second consumer appears
│ WHY: Premature abstraction, overhead > benefit
│
├─ Code used by 2+ apps → MAYBE split
│ ├─ Stable API (rarely changes) → Split
│ ├─ Unstable (changes every sprint) → DON'T split yet
│ └─ Mixed team ownership → DON'T split (use import path instead)
│ WHY: Shared packages need stable APIs + clear owners
│
├─ Publishing to npm → MUST split
│ └─ External packages require independent versioning
│
└─ CI builds too slow (> 10min) → Split strategically
└─ Split by stability (core vs features), not by domain
WHY: Stable packages cache, unstable packages rebuildAnti-pattern: Creating packages for "clean architecture" without consumers. Packages add overhead (build, test, version).
考虑拆分代码为独立包?
│
├─ 代码仅被1个应用使用 → 暂不拆分
│ └─ 直到出现第二个使用者前,保留在应用内
│ 原因:过早抽象,成本大于收益
│
├─ 代码被2个以上应用使用 → 可考虑拆分
│ ├─ API稳定(极少变更)→ 拆分
│ ├─ API不稳定(每个迭代都变更)→ 暂不拆分
│ └─ 多团队共同维护 → 暂不拆分(改用导入路径)
│ 原因:共享包需要稳定API和明确的负责人
│
├─ 需要发布到npm → 必须拆分
│ └─ 外部包需要独立版本管理
│
└─ CI构建过慢(>10分钟)→ 战略性拆分
└─ 按稳定性拆分(核心代码 vs 功能代码),而非按领域拆分
原因:稳定包可缓存,不稳定包需重新构建反模式:为了“整洁架构”而创建无使用者的包。包会增加额外成本(构建、测试、版本管理)。
Anti-Patterns
反模式
❌ #1: Circular Dependencies
❌ #1:循环依赖
Problem: Packages depend on each other, breaks task graph
packages/ui → imports from packages/utils
packages/utils → imports from packages/ui // ❌ CircularDetection:
bash
turbo run build # Fails with: "Could not resolve dependency graph"Fix: Extract shared code to third package
packages/ui → packages/shared
packages/utils → packages/sharedWhy it breaks: Turborepo builds dependencies first (topological sort). Circular deps = no valid build order.
Why this is deceptively hard to debug: Error message "Could not resolve dependency graph" doesn't mention the word "circular"—just lists package names. With 10+ packages, takes 15-20 minutes to manually trace imports and realize two packages reference each other. The import chain might be indirect (A → B → C → A), making it even harder to spot. Developers waste time checking turbo.json config and workspace setup before realizing it's an import cycle issue, not a Turborepo configuration problem.
问题:包之间互相依赖,破坏任务图
packages/ui → 导入 packages/utils
packages/utils → 导入 packages/ui // ❌ 循环依赖检测方法:
bash
turbo run build // 执行失败,提示:"Could not resolve dependency graph"修复方案:将共享代码提取到第三个包
packages/ui → packages/shared
packages/utils → packages/shared问题原因:Turborepo按拓扑排序优先构建依赖项。循环依赖导致没有有效的构建顺序。
调试难点:错误提示“Could not resolve dependency graph”未提及“循环”一词,仅列出包名。当包数量超过10个时,手动追踪导入关系并发现循环依赖需要15-20分钟。导入链可能是间接的(A→B→C→A),更难发现。开发者会先花费时间检查turbo.json配置和工作区设置,之后才意识到是导入循环问题,而非Turborepo配置问题。
❌ #2: Overly Granular Packages
❌ #2:过度细分的包
Problem: 50 micro-packages, every import crosses package boundary
packages/button/
packages/input/
packages/checkbox/
packages/radio/
packages/select/
// ... 45 more single-component packagesSymptoms:
- Every change touches 5+ packages
- 10+ version bumps per feature
- version hell
pnpm workspace:*
Fix: Group by stability/purpose
packages/ui/ # All components (changes often)
packages/ui-primitives/ # Headless components (stable)
packages/icons/ # Generated SVGs (rarely changes)Decision rule: Package boundary = different change frequency
Why this is deceptively hard to debug: Takes weeks or months to discover the problem—not immediate. First feature seems fine (update 3 packages, publish 3 versions). Second feature touches 5 packages. Third feature hits 10 packages and you're managing workspace version conflicts for 2 hours. The pain accumulates slowly: CI gets slower (building 50 packages), version bumps become tedious (changesets for 10+ packages), developers avoid refactoring because it crosses too many boundaries. Only after 3-6 months do you realize the granularity was wrong, but by then you have 50 packages and merging them requires major migration work.
问题:50个微包,每次导入都跨包边界
packages/button/
packages/input/
packages/checkbox/
packages/radio/
packages/select/
// ... 还有45个单一组件包症状:
- 每次变更涉及5个以上包
- 每个功能需要更新10个以上版本
- 版本管理混乱
pnpm workspace:*
修复方案:按稳定性/用途分组
packages/ui/ # 所有组件(频繁变更)
packages/ui-primitives/ # 无头组件(稳定)
packages/icons/ # 生成的SVG(极少变更)决策规则:包边界 = 不同的变更频率
调试难点:问题不会立即显现,需要数周或数月才会暴露。第一个功能似乎没问题(更新3个包,发布3个版本),第二个功能涉及5个包,第三个功能涉及10个包,此时开发者需要花费2小时解决工作区版本冲突。痛苦会逐渐累积:CI构建变慢(构建50个包),版本更新繁琐(为10个以上包创建变更集),开发者因跨包边界而避免重构。3-6个月后才会意识到包的粒度错误,但此时已有50个包,合并需要大量迁移工作。
❌ #3: Missing Task Dependencies
❌ #3:缺失任务依赖
Problem: Tests run before build completes
json
// turbo.json
{
"tasks": {
"build": { "outputs": ["dist/**"] },
"test": {} // ❌ No dependsOn
}
}
// Result: tests import from dist/ before it existsFix: Explicit dependencies
json
{
"tasks": {
"build": { "dependsOn": ["^build"], "outputs": ["dist/**"] },
"test": { "dependsOn": ["build"] } // ✅ Build first
}
}Why: = build dependencies first. = build this package first.
^buildbuildWhy this is deceptively hard to debug: Tests pass locally (you ran manually first), fail in CI with cryptic errors like "Cannot find module './dist/index.js'" or import errors. The race condition is timing-dependent—sometimes tests start before build finishes, sometimes they start after (especially with caching). Developers waste 10-15 minutes checking import paths, package.json exports, and tsconfig before realizing it's a task ordering issue. The error message points to the symptom (missing file) not the cause (missing dependency declaration).
build问题:测试在构建完成前执行
json
// turbo.json
{
"tasks": {
"build": { "outputs": ["dist/**"] },
"test": {} // ❌ 未设置dependsOn
}
}
// 结果:测试从dist/导入文件时,文件尚未生成修复方案:明确任务依赖
json
{
"tasks": {
"build": { "dependsOn": ["^build"], "outputs": ["dist/**"] },
"test": { "dependsOn": ["build"] } // ✅ 先执行构建
}
}说明: = 优先构建依赖包。 = 先构建当前包。
^buildbuild调试难点:本地测试通过(已手动执行build),但CI中失败,提示“Cannot find module './dist/index.js'”或导入错误。竞争条件与时间有关——有时测试在构建完成后启动,有时在构建完成前启动(尤其是启用缓存时)。开发者会花费10-15分钟检查导入路径、package.json导出配置和tsconfig,之后才意识到是任务顺序问题。错误提示指向症状(文件缺失)而非原因(未声明依赖)。
❌ #4: Cache Miss Hell
❌ #4:缓存未命中噩梦
Problem: Cache never hits, rebuilds everything
json
// turbo.json
{
"tasks": {
"build": {
"inputs": ["src/**"] // ❌ Too broad
}
}
}
// Any file change (even comments) = cache missFix: Exclude non-code files
json
{
"tasks": {
"build": {
"inputs": [
"src/**/*.{ts,tsx}", // ✅ Only source files
"!src/**/*.test.ts" // Exclude tests
]
}
}
}Debug cache:
bash
turbo run build --dry --graph # Shows why cache missedWhy this is deceptively hard to debug: Cache initially works (first few builds hit), then mysteriously stops. Every run shows "cache miss" but you can't tell why. The problem: you added a README.md to src/, touched a comment, or updated a test file—non-code changes that shouldn't trigger rebuild but do because inputs include . Developers waste 20-30 minutes checking remote cache credentials, clearing local cache, restarting daemon before realizing the input glob is too broad. The flag shows hash changed but doesn't clearly indicate WHICH file caused it (need to diff file lists manually).
src/**--dry --graph问题:缓存从未命中,每次都需全量构建
json
// turbo.json
{
"tasks": {
"build": {
"inputs": ["src/**"] // ❌ 范围过宽
}
}
}
// 任何文件变更(即使是注释)都会导致缓存未命中修复方案:排除非代码文件
json
{
"tasks": {
"build": {
"inputs": [
"src/**/*.{ts,tsx}", // ✅ 仅包含源码文件
"!src/**/*.test.ts" // 排除测试文件
]
}
}
}调试缓存:
bash
turbo run build --dry --graph // 显示缓存未命中原因调试难点:缓存最初可用(前几次构建命中),之后突然失效。每次运行都显示“cache miss”但无法确定原因。问题在于:你在src/中添加了README.md、修改了注释或更新了测试文件——这些非代码变更不应触发重构,但因inputs包含而导致缓存未命中。开发者会花费20-30分钟检查远程缓存凭证、清理本地缓存、重启守护进程,之后才意识到输入的glob模式范围过宽。标志显示哈希变更,但未明确指出是哪个文件导致的(需要手动对比文件列表)。
src/**--dry --graphDecision: Monorepo vs Polyrepo
决策:单体仓库(Monorepo) vs 多仓库(Polyrepo)
Starting new project?
│
├─ Single team, single product → Polyrepo (simpler)
│ └─ One repo per service/app
│ WHY: Monorepo overhead not worth it for small teams
│
├─ Shared UI library → Monorepo
│ └─ Library + consumer apps in same repo
│ WHY: Develop library + test in consumers simultaneously
│
├─ Microservices (different languages) → Polyrepo
│ └─ Go service, Python service, Node service
│ WHY: Turborepo is JS/TS focused, polyrepo simpler
│
└─ Multiple teams, shared code → Monorepo
└─ Need atomic changes across boundaries
WHY: One PR changes API + all consumersReal-world: Most projects should start polyrepo, migrate to monorepo when pain > tooling cost.
启动新项目?
│
├─ 单一团队,单一产品 → 多仓库(更简单)
│ └─ 每个服务/应用对应一个仓库
│ 原因:小团队无需承担单体仓库的管理成本
│
├─ 共享UI库 → 单体仓库
│ └─ 库和消费应用在同一仓库
│ 原因:可同时开发库并在消费应用中测试
│
├─ 微服务(多语言)→ 多仓库
│ └─ Go服务、Python服务、Node服务
│ 原因:Turborepo专注于JS/TS,多仓库更简单
│
└─ 多团队,共享代码 → 单体仓库
└─ 需要跨边界的原子变更
原因:一个PR即可更新API和所有消费端实际经验:大多数项目应从多仓库开始,当协作痛苦超过工具成本时再迁移到单体仓库。
Package Boundary Patterns
包边界模式
Pattern 1: By Stability
模式1:按稳定性划分
packages/
core/ # Changes quarterly (semantic versioning)
features/ # Changes weekly (workspace protocol)
utils/ # Changes monthlyBenefit: Stable packages cache longer, ship to npm independently.
packages/
core/ # 每季度变更(语义化版本管理)
features/ # 每周变更(工作区协议)
utils/ # 每月变更优势:稳定包可长期缓存,可独立发布到npm。
Pattern 2: By Consumer
模式2:按消费者划分
packages/
public-api/ # External consumers
internal/ # Internal apps onlyBenefit: Clear API surface, different versioning strategies.
packages/
public-api/ # 外部消费者
internal/ # 仅内部应用使用优势:API边界清晰,可采用不同的版本管理策略。
Pattern 3: By Team
模式3:按团队划分
packages/
team-platform/
team-growth/
team-infra/Warning: Only works if teams rarely share code. Otherwise creates silos.
packages/
team-platform/
team-growth/
team-infra/警告:仅适用于团队间极少共享代码的场景,否则会造成信息孤岛。
Turborepo vs Alternatives
Turborepo vs 替代方案
Choose Turborepo when:
✅ JS/TS monorepo (React, Next.js, Node)
✅ Need remote caching (Vercel, self-hosted)
✅ Task graph parallelization important
✅ Using pnpm workspaces or npm workspaces
Choose Nx when:
✅ Need project graph visualization
✅ Polyglot (JS + Python + Go)
✅ Want opinionated project structure
✅ Need plugin ecosystem
Choose Rush when:
✅ Very large monorepo (100+ packages)
✅ Need phantom dependencies detection
✅ Publishing to npm is primary use caseReal-world: Turborepo wins for Next.js/React apps, Nx wins for complex polyglot, Rush wins for library publishers.
选择Turborepo的场景:
✅ JS/TS单体仓库(React、Next.js、Node)
✅ 需要远程缓存(Vercel、自托管)
✅ 任务图并行化至关重要
✅ 使用pnpm工作区或npm工作区
选择Nx的场景:
✅ 需要项目图可视化
✅ 多语言混合(JS + Python + Go)
✅ 偏好约定式项目结构
✅ 需要插件生态系统
选择Rush的场景:
✅ 超大规模单体仓库(100+包)
✅ 需要检测幽灵依赖
✅ 主要用途是发布到npm实际经验:Turborepo适合Next.js/React应用,Nx适合复杂多语言项目,Rush适合库发布者。
Debugging Commands
调试命令
Visualize task graph
可视化任务图
bash
turbo run build --dry --graph=graph.htmlbash
turbo run build --dry --graph=graph.htmlOpens browser with task dependency visualization
在浏览器中打开任务依赖可视化图
undefinedundefinedFind cache misses
查找缓存未命中
bash
turbo run build --dry=json | jq '.tasks[] | select(.cache.status == "MISS")'bash
turbo run build --dry=json | jq '.tasks[] | select(.cache.status == "MISS")'Check package dependency order
检查包依赖顺序
bash
turbo run build --dry --graph | grep "→"bash
turbo run build --dry --graph | grep "→"Test cache without running tasks
测试缓存(不执行任务)
bash
turbo run build --dry # Shows what would runbash
turbo run build --dry # 显示将要执行的任务Error Recovery Procedures
错误恢复流程
When Cache Never Hits (Cache Miss Hell)
缓存从未命中(缓存未命中噩梦)
Recovery steps:
- Diagnose: Run to see current hash
turbo run build --dry=json | jq '.tasks[0].hash' - Identify culprit: Add to see which files changed the hash
--log-order=grouped - Fix inputs: Narrow glob patterns to exclude non-code files (tests, docs, configs)
- Fallback: If still missing, disable cache for that task temporarily: in turbo.json, then debug without cache pressure
"cache": false
恢复步骤:
- 诊断:执行查看当前哈希
turbo run build --dry=json | jq '.tasks[0].hash' - 定位问题:添加参数查看哪些文件导致哈希变更
--log-order=grouped - 修复输入配置:缩小glob模式范围,排除非代码文件(测试、文档、配置)
- 备选方案:若仍未解决,临时禁用该任务的缓存:在turbo.json中设置,然后在无缓存压力下调试
"cache": false
When Circular Dependency Error Occurs
出现循环依赖错误
Recovery steps:
- Visualize: Run and open in browser
turbo run build --dry --graph=graph.html - Trace cycle: Look for packages that appear in each other's dependency chains (A → B → ... → A)
- Extract shared: Create new package (e.g., ) and move common code there
packages/shared - Fallback: If cycle is complex (3+ packages), use dependency graph tool like to visualize:
madgenpx madge --circular --extensions ts,tsx packages/
恢复步骤:
- 可视化:执行并在浏览器中打开
turbo run build --dry --graph=graph.html - 追踪循环:查找在彼此依赖链中出现的包(A→B→...→A)
- 提取共享代码:创建新包(如)并将公共代码迁移至此
packages/shared - 备选方案:若循环复杂(涉及3个以上包),使用依赖图工具可视化:
madgenpx madge --circular --extensions ts,tsx packages/
When Tests Fail in CI But Pass Locally
CI中测试失败但本地通过
Recovery steps:
- Check task order: Run to see if build runs before test
turbo run test --dry --graph - Add dependencies: Add to test task in turbo.json
"dependsOn": ["build"] - Verify: Run (bypass cache) to confirm tests pass when build runs first
turbo run test --force - Fallback: If still failing, check for race condition in parallel tests: add to test task temporarily and see if issue persists
"cache": false
恢复步骤:
- 检查任务顺序:执行查看构建是否在测试前执行
turbo run test --dry --graph - 添加依赖:在turbo.json的test任务中添加
"dependsOn": ["build"] - 验证:执行(绕过缓存)确认先构建再测试可通过
turbo run test --force - 备选方案:若仍失败,检查并行测试的竞争条件:临时禁用测试任务的缓存,查看问题是否持续
"cache": false
When Overly Granular Packages Cause Version Hell
过度细分的包导致版本混乱
Recovery steps:
- Audit changes: Run to count package version bumps
git log --oneline --since="1 month ago" -- packages/ - Identify clusters: Look for packages that always change together (5+ times in last month)
- Merge packages: Combine related packages into single package with internal structure
- Fallback: If merging is too risky, use protocol to auto-link versions and reduce manual bumps
workspace:*
恢复步骤:
- 审计变更:执行统计包版本更新次数
git log --oneline --since="1 month ago" -- packages/ - 识别集群:查找总是一起变更的包(过去1个月变更5次以上)
- 合并包:将相关包合并为单个包,保留内部结构
- 备选方案:若合并风险过高,使用协议自动链接版本,减少手动更新
workspace:*
When to Load Full Reference
何时查阅完整参考文档
MANDATORY - READ ENTIRE FILE: when:
references/cli-options.md- Encountering 3+ unknown CLI flags in error messages or commands
- Need advanced filtering across 10+ packages (--filter patterns, --affected usage)
- Setting up 5+ complex task pipeline options (--concurrency, --continue, --output-logs)
- Troubleshooting CLI behavior that's not covered in this core framework
MANDATORY - READ ENTIRE FILE: when:
references/remote-cache-setup.md- Setting up remote cache for team with 3+ developers
- Debugging 5+ cache authentication or connection errors
- Configuring self-hosted remote cache with custom storage backend
- Implementing cache security policies (signature verification, access control)
Do NOT load references for:
- Basic architecture decisions (use this core framework)
- Single cache miss debugging (use Error Recovery section above)
- Deciding whether to adopt monorepo (use Strategic Assessment section)
必须阅读完整文档:适用于以下场景:
references/cli-options.md- 在错误信息或命令中遇到3个以上未知CLI标志
- 需要对10个以上包进行高级过滤(--filter模式、--affected用法)
- 设置5个以上复杂任务流水线选项(--concurrency、--continue、--output-logs)
- 排查本核心框架未覆盖的CLI行为问题
必须阅读完整文档:适用于以下场景:
references/remote-cache-setup.md- 为3名以上开发者的团队设置远程缓存
- 排查5个以上缓存认证或连接错误
- 配置自定义存储后端的自托管远程缓存
- 实施缓存安全策略(签名验证、访问控制)
无需查阅参考文档:
- 基础架构决策(使用本核心框架即可)
- 单一缓存未命中调试(使用上述错误恢复部分)
- 决定是否采用单体仓库(使用战略评估部分)
Resources
资源
- Official Docs: https://turbo.build/repo/docs (for CLI reference)
- This Skill: Architecture decisions, anti-patterns, package boundaries
- 官方文档:https://turbo.build/repo/docs(CLI参考)
- 本指南:架构决策、反模式、包边界