在 Vercel 上从 Turborepo monorepo 部署内容站

从 Turborepo monorepo 把内容站部到 Vercel 的精确配置:每个 app 单独的 Root Directory、turbo build、带 --fallback 的 turbo-ignore,以及不发布也能用的共享包。2026 年 6 月核实。

一个 Turborepo 里同时放营销站、文档站、Web 应用,2026 年是常见做法。Vercel 能把它干净地部上去,前提是每个 project 三个设置都对:Root Directory、build 命令、Ignored Build Step。配错了要么每次 push 三个 app 全部重建(慢,而且在 Pro 套餐上烧的是付费 build 分钟),要么直接报 Module not found: @workspace/ui

本文给出截至 2026 年 6 月、Vercel 官方文档推荐的精确配置,外加两个最坑人的 bug:新分支全量重建,以及缓存命中后的”产物找不到”。

太长不看

  • 一个 app 对应一个 Vercel project。一个 project 只部署一种框架的一次构建。
  • app 放在 apps/ 下,每个 project 的 Root Directory 设成 apps/<name>
  • Build 命令就写 turbo build——Turbo 1.8+ 会从 Root Directory 自动推断 filter。不用把 turbo 加成依赖,Vercel 上全局就有。
  • Ignored Build Step:npx turbo-ignore --fallback=HEAD^1--fallback 这个参数才是阻止每个新分支全量重建的关键。
  • 共享包用 transpilePackages(Next.js)或 dist/ 构建步骤接进来——不用发到 npm。

monorepo 怎么映射到 Vercel

一个 Vercel project 用一种框架预设部署一个 app。所以 N 个 app 的 monorepo 就是 N 个 Vercel project,每个指向同一个 Git 仓库里的不同子目录。每个 project 都监听整个仓库的 push;具体哪个 project 真去构建,由各自的 Root Directory 和 Ignored Build Step 决定。

Turborepo 负责构建图:它知道 apps/www 依赖 packages/ui,按内容 hash 缓存任务产物,并提供 turbo-ignore,让只改文档的提交不会去重建营销站。

先说一个对内容站特别要命的商用限制:Vercel 的 Hobby 套餐仅限非商业用途(Vercel 的 Fair Use Guidelines 在 2026 年 2 月再次确认)。任何挂广告、放联盟链接、或与盈利相关的站点,都必须上 Pro(每席位 20 美元/月)。一堆盈利内容站组成的 monorepo,毫无疑问属于商业用途。

配错了的征兆

  • apps/docs 改一行,三个 project 一起重建。
  • build log 报 Module not found: Can't resolve '@workspace/ui'
  • Vercel 从仓库根目录跑了 build 命令,结果部了错的 app。
  • turbo build 成功了,但缓存命中时 Vercel 说输出目录是空的。
  • 每个新 PR 分支都全量重建,哪怕这个 app 啥都没改。

能跑的项目结构

my-monorepo/
├── apps/
│   ├── www/                 → 营销站(Astro)
│   ├── docs/                → 文档站(Next.js)
│   └── app/                 → Web 应用(Next.js)
├── packages/
│   ├── ui/                  → 共享 React 组件
│   ├── config-tsconfig/     → 共享 tsconfig
│   └── eslint-config/       → 共享 lint
├── turbo.json
├── package.json             → workspaces: ["apps/*", "packages/*"]
└── pnpm-workspace.yaml      → 用 pnpm 时

每个 apps/<name>/package.json 把共享包当普通依赖声明:pnpm 写 "@workspace/ui": "workspace:*",npm/yarn workspaces 写 "@workspace/ui": "*"

每个 app 的 Vercel 设置(2026 年 6 月)

下面这些就是你导入 Turborepo 时 Vercel 自动识别的值,取自官方部署文档。在每个 project 的 Settings → General 里设:

设置项说明
Root Directoryapps/www最常被跳过的一步;多数”module not found”都因它而起。
Framework PresetAstro / Next.js35+ 预设之一,自动识别。
Build Commandturbo buildTurbo 1.8+ 会从 Root Directory 推断 --filter
Install Command用默认Vercel 自动识别 pnpm/npm/yarn,在仓库根目录装整个 workspace。
Output Directory框架默认Astro 是 dist,Next.js 是 .next
Ignored Build Stepnpx turbo-ignore --fallback=HEAD^1这个 app 的依赖图没变化时跳过构建。

这里有两点常被搞错:

  1. Build 命令。 不用把 turbo 装成依赖——每次 Vercel 构建时它都全局可用,而且从 Turbo 1.8 起 filter 是从 Root Directory 推断的,所以一句 turbo build 就够。老式长写法 cd ../.. && turbo run build --filter=www 也仍然能用,想显式声明就用它。
  2. Node 版本。 在仓库根 package.jsonengines.node 里锁一次,让每个 project 都用同一个大版本;否则不同 project 可能落到不同 Node 版本上。

为什么 Ignored Build Step 里要加 --fallback

光写 npx turbo-ignore 是跟上一次成功部署做对比。在一个全新分支上,这个分支还没有过任何部署,于是它保守起见就去构建——结果每个新 PR 都把所有 app 全量重建,意义全失。加上 --fallback=HEAD^1 是告诉它:没有历史部署时就跟上一个 commit 做 diff,于是只有真正改动过的 app 才构建。这正是 Vercel 导入时自己设的值,也是那个有名的”turbo-ignore 在新分支上总是构建所有 project”问题的解法。

不用 Turbo 的 workspace,手动等价写法(在每个 app 的 Root Directory 里跑):

git diff HEAD^ HEAD --quiet . ../../packages/ui ../../packages/config-tsconfig

这条命令退出 0(app 及其共享依赖都没变化)时 Vercel 跳过构建。

共享包不发布也能用

两种模式,选一种:

  1. 构建时转译(Next.js)。 让使用方 app 在构建时编译共享的 TypeScript:
/** @type {import('next').NextConfig} */
module.exports = {
  transpilePackages: ['@workspace/ui', '@workspace/config-tsconfig'],
};
  1. 预构建包。 每个包有 build 脚本输出到 dist/,它的 package.jsonmain/types 指到构建产物。因为 build 任务在 turbo.json 里声明了 "dependsOn": ["^build"],Turbo 会先构建依赖再构建依赖方。

内容站如果只是引一个共享组件库,模式 1 更简单。包还要发到 npm 时,模式 2 扩展性更好。

缓存命中后”产物找不到”的坑

一种常见故障:turbo build 命中 Turborepo 缓存,却没往预期的输出目录里恢复任何东西,于是 Vercel 报”构建没有产生输出”。原因几乎都是框架真实输出和 turbo.jsonoutputs 键对不上。把它们对齐:

{
  "$schema": "https://turborepo.com/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "!.next/cache/**", "dist/**", ".vercel/output/**"]
    }
  }
}

本地跑一遍 turbo build,确认它产出的目录和 Vercel 对你这个框架预设的预期一致。还有两个相关注意点(截至 2026 年 6 月):Next.js 的 Skew Protection 一定会造成 Turborepo 缓存未命中(它会往 hash 里注入一个每次部署都变的环境变量);而 2.4.1 以下的 Turborepo 在 Skew Protection 下可能漏发资源,所以升级到 2.4.1 或更高。

成本:monorepo 实际用掉多少

build 分钟是大家最容易忘记估的那一项。截至 2026 年 6 月:

项目HobbyPro
价格免费(仅限非商业)每席位 20 美元/月,含 20 美元额度
并发构建112
Project 数200更高
每日部署100更高
单次构建时长上限45 分钟/次45 分钟/次
build 分钟不含标准机约 0.014 美元/分钟

实操结论:三个 app 的盈利内容站 monorepo 应该上 Pro,而 turbo-ignore 是你压低付费 build 分钟的主要杠杆。不接它,一个 app 的 README 里改个错字,就可能触发三次全量构建。

容易踩的坑

  • Root Directory 留在 /,每个 project 都构建同一个(错的)app。
  • build 命令里在仓库根目录写死 npm install,没让 Vercel 识别 workspace——pnpm/yarn workspaces 会坏。
  • npx turbo-ignore 不带 --fallback——每个新分支都全量重建。
  • turbo.jsonoutputs 和框架真实输出目录对不上——缓存命中时”产物找不到”。
  • 没在根 package.json 锁 Node——各 project 漂到不同 Node 大版本上。
  • 两个 app 都要的环境变量只配在一个 project 里——env 在 Vercel 上是按 project 单独作用域的。
  • 把商业内容站跑在 Hobby 上——违反 Fair Use Guidelines,可能被封号。

FAQ

  • Vercel 部署 monorepo 一定要用 Turbo 吗?: 不用。Vercel 直接支持 npm/yarn/pnpm workspaces。Turbo 加的是缓存和 turbo-ignore。两三个 app 通常 workspaces 就够,但你会失去选择性构建。
  • 一个 Vercel project 能部多个框架吗?: 不能。一个 project = 一种框架的一次构建。一个 app 开一个 project。
  • 现在 Vercel 推荐的 build 命令到底是什么?: turbo build(Turbo 1.8+,filter 从 Root Directory 推断)。显式的 cd ../.. && turbo run build --filter=<app> 也行。
  • 为什么我的新分支总是全量重建?: 你的 turbo-ignore 少了 --fallback=HEAD^1。全新分支上没有历史部署,光 turbo-ignore 会保守地去构建。
  • 用 Nx 行不行?: 形态一样——每个 app 设 Root Directory,build 命令写 nx build <app>,Ignored Build Step 里用 Nx 的 affected 命令。
  • 内容站能跑在免费 Hobby 上吗?: 只有真正非商业时才行。挂广告或联盟收入就算商业用途,按 Vercel 的 Fair Use Guidelines 必须上 Pro。

相关阅读

外部参考:Vercel: Deploying TurborepoVercel Fair Use Guidelines

标签: #独立开发 #Vercel #部署 / 托管 #monorepo #工作流