Turbo 的 env 模型
默认(strict 模式):
任务启动时,只能看到:
- PATH / HOME 等系统变量
- 显式声明在 env/globalEnv/passThroughEnv 里的变量
其它 process.env 全部被过滤掉
意图:让任务可复现——两个人同一个 commit,即使本地 .env 不同,build 结果一致。
三种声明位置
{
"globalEnv": ["NODE_ENV", "VERCEL_ENV", "CI"],
"globalPassThroughEnv": ["GITHUB_TOKEN"],
"tasks": {
"build": {
"env": ["NEXT_PUBLIC_*", "DATABASE_URL"],
"passThroughEnv": ["AWS_*"]
}
}
}
globalEnv
仓库级——所有任务都会读到,且值变 → 所有任务 hash 变。适合
NODE_ENV、CI 这种全局影响构建行为的变量。tasks.xxx.env
任务级——只该任务读到,值变只影响该任务的 hash。适合任务专属的 API_URL、TOKEN 等。
passThroughEnv / globalPassThroughEnv
"透传但不进 hash"——任务能读这个变量,但它变了不触发重 build。典型:登录凭证、CI token。
通配符
{
"tasks": {
"build": {
"env": [
"NEXT_PUBLIC_*", // 所有 NEXT_PUBLIC_ 开头
"!NEXT_PUBLIC_DEBUG", // 除了 DEBUG
"VITE_*"
]
}
}
}
Next.js / Vite 这种带前缀约定的项目,通配非常省事——加新 NEXT_PUBLIC_X 不用改 turbo.json。
env 和 passThroughEnv 的选择
| 场景 | 用 env | 用 passThroughEnv |
|---|---|---|
| NEXT_PUBLIC_API_URL(被编译进产物) | ✓ | ✗ |
| DATABASE_URL(build 时用到) | ✓ | ✗ |
| GITHUB_TOKEN(只给 CI 用,变了不该重 build) | ✗ | ✓ |
| AWS_ACCESS_KEY(部署时用,不影响产物) | ✗ | ✓ |
| CI_JOB_ID(每次都变,进 hash 就废了缓存) | ✗ | ✓ |
判断原则
这个变量的值变化,是否影响产物内容?影响 → env;不影响 → passThroughEnv。
这个变量的值变化,是否影响产物内容?影响 → env;不影响 → passThroughEnv。
.env 文件集成
.env DATABASE_URL=postgres://localhost/myapp STRIPE_SECRET=sk_xxx .env.local DEBUG=true
Turbo 本身不加载 .env——靠你的构建工具(Next.js/Vite)自己加载。Turbo 只负责声明和 hash。
// turbo.json { "globalDependencies": [".env*"], // .env 文件变化触发 hash 变 "tasks": { "build": { "env": ["DATABASE_URL", "STRIPE_SECRET"] } } }
envMode 的三种值
{
"envMode": "strict"
}
strict(Turbo 2.x 默认)
任务只能看到声明过的 env——未声明的读到就是 undefined。最安全,鼓励显式声明。
loose(旧版默认)
任务可以读所有 env,但只有声明过的进 hash——方便但不安全,容易缓存错误命中。
# 临时切回 loose 模式调试 pnpm turbo run build --env-mode=loose
strict 模式的坑
// apps/web/next.config.js if (process.env.MY_CUSTOM_FLAG === "1") { ... } // turbo.json 没声明 MY_CUSTOM_FLAG // → strict 模式下 process.env.MY_CUSTOM_FLAG === undefined,if 进不去 // → 本地 export MY_CUSTOM_FLAG=1 也不行
解决:
{
"tasks": {
"build": {
"env": ["MY_CUSTOM_FLAG"]
}
}
}
调试:看任务实际收到的 env
pnpm turbo run build --dry-run=json | jq '.tasks[] | {task: .taskId, env: .resolvedTaskDefinition.env, envValues: .environmentVariables}'
{
"task": "@myorg/web#build",
"env": ["NEXT_PUBLIC_*", "DATABASE_URL"],
"envValues": {
"configured": ["NEXT_PUBLIC_API_URL=https://api.example.com"],
"inferred": ["NEXT_PUBLIC_STRIPE_KEY=pk_xxx"] # 通配匹配到的
}
}
Vercel 集成
Vercel dashboard → Project Settings → Environment Variables: DATABASE_URL=... (production) DATABASE_URL=... (preview) NEXT_PUBLIC_API_URL=... Vercel 在 build 时把这些环境变量注入,Turbo 读它们算 hash → 不同环境 hash 不同 → Preview 和 Production 各自缓存
Vercel 自动推断 env
Vercel 能从 Next.js 代码里扫出你用了哪些 env,在 build 时自动注入。配合 Turbo,你只需要在 turbo.json 显式列上这些——不用重复在 vercel.json 里写一遍。
Vercel 能从 Next.js 代码里扫出你用了哪些 env,在 build 时自动注入。配合 Turbo,你只需要在 turbo.json 显式列上这些——不用重复在 vercel.json 里写一遍。
本地不同开发者的 .env.local
{
"globalPassThroughEnv": ["LOCAL_DEV_*"]
}
开发者各自的 LOCAL_DEV_PORT=3001、LOCAL_DEV_DB=postgres://me/...——这些不该影响缓存(影响了就没人命中了),用 passThroughEnv。
env 分组:多阶段配置
{
"tasks": {
"build": {
"env": ["NEXT_PUBLIC_*"]
},
"build:staging": {
"env": ["NEXT_PUBLIC_*", "STAGING_*"]
},
"build:production": {
"env": ["NEXT_PUBLIC_*", "PROD_*"]
}
}
}
不同环境跑不同任务,env 声明也不同——产物天然不串。
env 作为动态 hash
两次 CI build: 第一次:DATABASE_URL=postgres://old,hash=abc 第二次:DATABASE_URL=postgres://new,hash=def Turbo 发现 hash 变了 → 重跑 → 产物里注入新 URL
这是对的——生产 DB 变了,产物应该重编。Turbo 的 env hash 机制保证了这点。
不要往代码里写死秘密
env 机制的价值前提
如果你在代码里硬编码了 API Key,Turbo 的 env hash 完全保护不了你——改 key 代码 diff 就会让 hash 变。
如果你在代码里硬编码了 API Key,Turbo 的 env hash 完全保护不了你——改 key 代码 diff 就会让 hash 变。
- 正确:
process.env.STRIPE_KEY(声明在 turbo.json env) - 错误:
const STRIPE_KEY = "sk_live_xxx"
CI 实际例子
# .github/workflows/ci.yml env: # 进 hash 的 NEXT_PUBLIC_API_URL: https://api.prod.example.com DATABASE_URL: ${{ secrets.DATABASE_URL }} # 不进 hash 的(passThroughEnv) TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} steps: - run: pnpm turbo run build
// turbo.json 对应 { "globalPassThroughEnv": ["TURBO_TOKEN", "GITHUB_TOKEN", "AWS_*"], "tasks": { "build": { "env": ["NEXT_PUBLIC_*", "DATABASE_URL"] } } }
检查 env 配置是否完整
# 改一个应该影响缓存的 env,看 hash 是否变 export DATABASE_URL=postgres://test1 pnpm turbo run build --dry-run=json | jq '.tasks[0].hash' # → "abc123..." export DATABASE_URL=postgres://test2 pnpm turbo run build --dry-run=json | jq '.tasks[0].hash' # → "def456..." ← 不同,说明 env 进了 hash,配对了
本章小结
- 三种 env 声明:
globalEnv(仓库级入 hash)、tasks.x.env(任务级入 hash)、passThroughEnv(透传不入 hash) - 通配
NEXT_PUBLIC_*+!PATTERN排除是最常用组合 - envMode
strict(2.x 默认)禁止读未声明的 env——鼓励显式声明 - 改变产物的变量进 env;秘密凭证进 passThroughEnv
globalDependencies: [".env*"]让 .env 文件进全局 hash