Monorepo 构建与部署:Turborepo、Nx 与 pnpm workspace 实战

系统讲解 Monorepo 工程的构建与部署实践:pnpm workspace 与依赖管理、Turborepo/Nx 任务编排与增量缓存、Affected 定向构建、构建产物共享与缓存命中、Monorepo 在 Vercel/Cloudflare 的部署策略、发布流程与版本管理(Changesets)、以及 Monorepo 的边界与选型。

一、引言

多仓库(Multi-repo)让「跨项目复用代码」要发私有包、让「一次改动涉及多处」要逐个仓库同步,而 Monorepo 把多个项目放进一个仓库,共享代码、统一 CI、一键联动部署。代价是「一个仓库可能很复杂」——构建编排、缓存、增量部署都成了新问题。

本文系统讲 Monorepo 的构建与部署:先建立 Monorepo 的结构与收益/代价认知,再深入 pnpm workspace 依赖管理、Turborepo/Nx 任务编排与缓存、Affected 定向构建,接着讲 Vercel/Cloudflare 上的部署策略与发布流程(Changesets),最后给出边界与选型建议。

关联:https://plumephp.com/tools-deployment-rollback-strategies/(部署策略)、https://plumephp.com/vercel-deploy-vue-nuxt/(多框架部署)、https://plumephp.com/tools-nextjs-app-router-deep/(Next.js 工程)。


二、Monorepo 的收益与代价

2.1 典型结构

apps/
  web/        # Next.js 前端
  api/        # Node API
  worker/     # 边缘函数
packages/
  ui/         # 共享组件库
  utils/      # 共享工具
  types/      # 共享类型
configs/
  eslint/     # 共享配置
  tsconfig/   # 共享 TS 配置

2.2 收益 vs 代价

收益代价
共享代码(ui/utils/types)构建编排复杂
一次改动联动多包构建时间可能爆炸
统一 lint/测试/CI依赖关系难管理
原子提交(跨包一致)权限粒度粗
便于 Code Review 联动新人上手门槛高

2.3 什么时候该上 Monorepo

适合:多包共享代码、前后端联动频繁、需要原子发布
不适合:团队大且耦合低、各包独立发布节奏、权限隔离要求高

一句话总结:Monorepo 换「共享+联动+原子提交」,代价是「构建编排+复杂度」——多包强耦合时收益大于代价。


三、pnpm workspace 依赖管理

3.1 为什么是 pnpm

pnpm 三大特性:
  1. 内容寻址存储(硬链接)→ 省磁盘、省安装
  2. 严格依赖隔离(非扁平 node_modules)→ 不会「幽灵依赖」
  3. workspace 原生支持 → 跨包引用直接 link

3.2 workspace 配置

# pnpm-workspace.yaml
packages:
  - "apps/*"
  - "packages/*"
# 常用命令
pnpm install                      # 安装全部
pnpm --filter web dev             # 只跑 web
pnpm --filter ui build            # 只构建 ui
pnpm -r run test                  # 所有包跑 test
// apps/web/package.json 引用本地包
{
  "dependencies": {
    "@repo/ui": "workspace:*",    // 本地 link
    "@repo/utils": "workspace:*"
  }
}

3.3 workspace 的坑

1. peer 依赖重复(React 多副本)→ 用 catalog / overrides 统一版本
2. 循环依赖(ui 依赖 utils,utils 依赖 ui)→ 拆包消除
3. 版本漂移(不同包用不同 React 版本)→ 统一 catalog
# pnpm-workspace.yaml 支持 catalog 统一版本
catalog:
  react: ^19.0.0
  typescript: ^5.5.0

一句话总结:pnpm 用硬链接省空间、严格隔离防幽灵依赖、workspace 原生支持本地 link;统一版本靠 catalog、防循环依赖靠拆包。


四、Turborepo / Nx 任务编排与缓存

4.1 问题:全量构建很慢

多包 → 每个包都 build/lint/test → 全量跑很慢
解法:任务编排(只跑受影响)+ 缓存(重复任务秒回)

4.2 Turborepo 配置

// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],   // 先构建依赖
      "outputs": [".next/**", "dist/**"],
      "cache": true
    },
    "lint": {},
    "test": {}
  }
}
pnpm turbo build            # 全量(首次)
pnpm turbo build --filter=web   # 只构建 web 及依赖
pnpm turbo ls                # 查看任务图

4.3 Turborepo 缓存命中

缓存键 = 代码哈希 + 配置 + 依赖 → 未变则命中缓存
命中 = 直接从缓存拿产物(秒级),不重新执行
远程缓存(Turborepo Remote Cache / Vercel)→ CI 与本地共享缓存
# 开启远程缓存(Turborepo + Vercel)
pnpm dlx turbo login
pnpm turbo build --remote-cache

4.4 Nx 对照

Nx 额外能力:
  1. Affected 分析(git 变更 → 只跑相关任务)
  2. 图可视化(nx graph)
  3. 代码生成(nx generate)
Turborepo 更轻量、聚焦缓存;Nx 功能全、学习成本高

一句话总结:Turborepo/Nx 的核心是「只跑受影响 + 缓存秒回」——dependsOn 保证依赖顺序、远程缓存让 CI 与本地共享;Nx 在 Affected 与生成上更强、Turborepo 更轻量。


五、Affected 定向构建

5.1 只构建「受影响的包」

改动 packages/utils → 只需构建依赖 utils 的包,无关包跳过
实现:
  Turborepo:缓存自动处理(未变的包命中缓存)
  Nx:nx affected:build --base=main
# Nx Affected
npx nx affected:build --base=origin/main   # 对比 main 的变更影响
npx nx affected:test --base=origin/main

# Turborepo:缓存天然实现「未变即命中」
pnpm turbo build --filter=[origin/main...HEAD]   # 变更范围

5.2 CI 中的增量策略

# 示例:CI 用 affected 缩短构建时间
jobs:
  build:
    steps:
      - checkout
      - run: pnpm install
      - run: npx nx affected:build --base=origin/main   # 只构建受影响
      - run: npx nx affected:test --base=origin/main

5.3 影响面分析的价值

1. 小改动不再全量构建 → CI 快 10 倍
2. 只部署受影响的 app → 发布快、风险小
3. 全量构建作为 main 的定期兜底

一句话总结:Affected/缓存让「改小包不触发全量构建」——Turborepo 缓存天然实现、Nx 显式 affected 分析;CI 增量构建是 Monorepo 提速的核心。


六、Monorepo 在 Vercel / Cloudflare 的部署

6.1 Vercel 的 Monorepo 支持

# Vercel 自动识别 Monorepo,按目录部署
# 根目录/目录配置
vercel.json:
{
  "buildCommand": "pnpm --filter web build",
  "outputDirectory": "apps/web/.next"
}
// 多个应用各自部署
// 每个 app 有独立 vercel.json,或用 workspace 自动探测
{
  "projectSettings": {
    "framework": "nextjs",
    "rootDirectory": "apps/web"
  }
}

6.2 Cloudflare 部署 Monorepo 子应用

# 在 apps/worker 内
cd apps/worker
wrangler deploy

# CI 中按 affected 决定部署哪些子应用
if [ "$(git diff --name-only origin/main...HEAD | grep -c '^apps/worker/')" -gt 0 ]; then
  pnpm --filter worker deploy
fi

6.3 部署的最佳实践

1. 只部署受影响的 app(affected)
2. 每个 app 独立 preview URL(Vercel 自动)
3. 构建产物缓存共享(远程缓存)
4. 环境变量按 app 维度管理
5. 依赖安装一次(根目录 pnpm install)→ 各 app 复用

一句话总结:Monorepo 部署 = Vercel/Cloudflare 识别子目录 + affected 决定部署哪些 + 远程缓存共享产物——每个 app 独立 preview、一次安装、增量部署。


七、发布与版本管理(Changesets)

7.1 版本策略:单版本 vs 独立版本

单版本(Turborepo 风格):所有包同一版本 → 简单,联动发布
独立版本:各包独立 → 灵活,但协调复杂

7.2 Changesets 工作流

# 1. 提交变更说明
pnpm changeset add          # 选择受影响包 + 版本类型(major/minor/patch)

# 2. 合并后 CI 生成 changelog + 升版本
pnpm changeset version      # 根据 changesets 更新版本与 CHANGELOG

# 3. 发布
pnpm changeset publish      # npm publish + 打 tag
// .changeset/config.json
{
  "changelog": "@changesets/cli/changelog",
  "commit": false,
  "access": "public",
  "baseBranch": "main"
}

7.3 发布流水线

PR(带 changeset)→ 合并 main → CI 检查 changeset
→ changeset version(生成 changelog)→ 发布 → 打 tag → 通知

一句话总结:Changesets 把「版本与发布」写进 PR——每个 PR 声明影响与版本类型,合并后自动生成 changelog 并发布。


八、Monorepo 的边界与选型

8.1 什么时候别用 Monorepo

1. 团队独立、发布节奏完全独立
2. 代码共享需求低
3. 仓库规模过大导致工具链卡顿
4. 权限隔离要求严格(多租户/外包)

8.2 工具链选型

场景推荐
依赖管理pnpm workspace
任务编排/缓存Turborepo(轻)或 Nx(全)
发布Changesets
部署Vercel/Cloudflare 原生 Monorepo 支持
后端 Monorepopnpm + Nx + 容器多镜像

8.3 渐进式采用

不一定要一步到位:
  先 2-3 个强耦合包合并 → 建立 workspace
  → 引入 Turborepo 缓存 → 再引入 affected/发布流水线
从「共享代码」起步,逐步演进

一句话总结:Monorepo 不是万能药——团队独立/权限隔离严格时别上;选型用 pnpm+Turborepo/Changesets+Vercel;渐进式从「共享代码」开始演进。


九、Monorepo 实践清单

  • pnpm workspace 配置正确,无循环依赖
  • 统一版本 catalog,无 React/TS 多副本
  • Turborepo/Nx 缓存命中正常(远程缓存共享)
  • affected 增量构建在 CI 生效
  • 每个 app 独立部署入口与 preview URL
  • Changesets 覆盖所有发布包
  • 根目录 lint/typecheck 全量兜底
  • 构建超时与缓存失效策略明确

十、速查表

需求方案
依赖管理pnpm workspace
任务编排Turborepo / Nx
增量缓存远程缓存 + affected
只构建受影响nx affected / turbo filter
版本管理Changesets
部署子应用Vercel/Cloudflare 目录配置
多包共享workspace link
统一版本catalog / overrides
防幽灵依赖pnpm 严格隔离
发布流水线changeset → version → publish

一句话记忆:Monorepo 用共享代码换联动效率——pnpm workspace 管依赖(硬链接省空间、隔离防幽灵)、Turborepo/Nx 管任务(缓存秒回 + affected 定向)、Changesets 管发布(PR 带版本声明自动发版);部署在 Vercel/Cloudflare 按子目录 + affected 增量;团队独立或权限隔离严格时别硬上——先从「共享代码」渐进演进。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「tools」更多文章

  1. 部署与回滚策略:蓝绿、金丝雀与不可变部署实战
  2. 边缘认证与会话管理:JWT、Cookie 与 Serverless 登录实战
  3. 可观测性与错误追踪:日志、Trace 与告警闭环