升级了某个依赖,本地 npm install 跑完、本地 build 也完全 OK,但推到 Cloudflare Pages 后线上依旧是旧版本,或者构建”成功”了线上却还是改之前的产物。原因是 Cloudflare Pages 复用了上一次构建缓存里的依赖,而没有装上你的新依赖。
最快的修法(约 80% 的情况能解决):先确认更新后的 package-lock.json 已经 commit 并 push,然后在 dashboard 里清掉构建缓存重新部署。路径是 Workers & Pages → 你的项目 → Settings → Build → Build cache → Clear Cache,再去 Deployments → Retry deployment。下文讲怎么判断自己属于哪种情况,以及怎么彻底防止复发。
缓存到底是怎么运作的(截至 2026 年 6 月)
当前 V3 构建系统的两个事实,几乎能解释所有”发旧版”的报告:
- Pages 并不会原样缓存你的
node_modules目录,它缓存的是包管理器的缓存目录(npm 的.npm、.cache/yarn、.pnpm-store、.bun/install/cache)以及框架的构建产物缓存(如.next/cache)。命中缓存时它会先恢复这些目录,然后照样跑一遍安装步骤——但严格安装是按你已提交的 lockfile 来装的,所以 lockfile 是旧的,装出来的依赖就是旧的。 - 只要仓库里有已提交的 lockfile,Pages 默认就已经用严格安装(npm 用
npm ci、yarn 用--frozen-lockfile/--immutable、pnpm 用pnpm install --frozen-lockfile),你不需要自己去设。所以发旧版几乎从来不是”Pages 跑了宽松安装”,而是”Pages 按的那份 lockfile 是旧的”,或者”用错了构建镜像 / Node 版本,编出了错的产物”。
顺带说一下缓存保留策略:项目的构建缓存在最后一次被读取 7 天后会被清除(单项目缓存上限 10 GB),所以”干等一周”算是一种很慢的、撞上的修法。
你属于哪种情况?
| 构建日志里的症状 | 最可能的原因 | 跳到 |
|---|---|---|
安装步骤约 1 秒就过,显示 up to date | lockfile 陈旧/未提交,缓存被复用 | 原因 1–2,Step 1–2 |
npm ci 报 lock file ... can only install with an existing package-lock.json 或 out of sync | lockfile 缺失或不同步 | 原因 2,Step 2 |
| 构建日志第一行的 Node 版本不对 | 构建镜像 / Node 版本漂移 | 原因 3,Step 3 |
| 原生模块运行时崩溃,但构建”通过”了 | 缓存里是旧 Node 大版本编出的 binding | 原因 3,Step 1+3 |
| 日志顶部的命令不是你设的那条 | 构建命令 / preset 不一致 | 原因 4,Step 4 |
| 上传的文件列表和上次部署一模一样 | 输出目录被复用 | 原因 5,Step 5 |
常见原因
按命中率从高到低排。
1. 缓存的依赖是按陈旧 lockfile 恢复的
最常见。把 package.json 里某个依赖从 ^1.0.0 升到 ^2.0.0,本地 npm install 重新生成了 package-lock.json,但没 commit 新 lockfile 就推上去。Pages 恢复依赖缓存后,按仓库里实际那份 lockfile(还是 v1 的)安装,于是发的还是 v1。
构建日志里能看到:
Restoring from build cache...
Success: Finished restoring build cache
Installing project dependencies: npm clean-install
up to date in 1s
up to date in 1s 是典型信号——因为已提交的 lockfile 跟恢复的缓存本来就一致,根本没真正 install。
如何判断:本地 git status 看 package-lock.json 是否未提交;Pages 日志里 install 阶段几乎秒过、没有任何包下载。
2. package-lock.json 未提交、被忽略或有多份
有些仓库 .gitignore 里误把 lockfile 加进去,或者同时 commit 了多种 lockfile(npm + yarn + pnpm),Pages 选错了那一份。如果根本没有已提交的 lockfile,npm ci 会直接 fail,而不是悄悄装错东西。
如何判断:grep -E "lock|node_modules" .gitignore 确认 lockfile 没被忽略;ls *.lock* package-lock.json 2>/dev/null 看仓库里实际有哪些 lockfile。
3. 构建镜像 / Node 版本漂移,编出了错的产物
截至 2026 年 6 月,当前 V3 构建镜像默认用 Node 22.16.0(Ubuntu 22.04)。过去那个”Pages 默认 Node 18”的坑,现在只会影响仍然锁在 v2 构建镜像(Node 18.17.1)上的项目。v1 项目会在 2026-09-15、v2 项目会在 2027-02-23 自动迁到 V3,所以一个长期没动过的项目仍可能停在旧默认值上。better-sqlite3、sharp 这类原生依赖在不同 Node 大版本下编出的 binding 不同,某个 Node 版本下缓存的 binding 跟另一个版本不兼容,于是 build 成功但 runtime 崩溃。
如何判断:Pages 构建日志开头几行会打出构建镜像版本和 Node 版本,跟本地 node -v 对比。在 Settings → Build → Build system version 里看当前锁的镜像。
4. 实际跑的构建命令不是你以为的那条
Build command 设的是 npm run build,但 framework preset 在前面加了自己的一步;或者你在 env var 里改了命令,而 deploy 还在用项目设置里保存的那条。
如何判断:构建日志顶部看实际执行的命令字符串,跟 Settings → Build → Build command 对比。
5. 输出目录也被缓存
更少见,但如果用了自定义 build script 把上次输出 cp 出来(或框架的构建产物缓存如 .next/cache 盖住了改动),Pages 就可能发布陈旧的资源。
如何判断:构建日志里 “Uploading” / “Deploying” 阶段的文件列表,对比本地构建产物(dist/)。
最短修复路径
Step 1:先提交 lockfile,再清构建缓存做一次冷部署
先确认新 lockfile 真的进了仓库(见下面 Step 2),然后在 dashboard:
Workers & Pages → 你的项目 → Settings → Build → Build cache → Clear Cache
清完后去 Deployments → Retry deployment 触发一次完全冷启动的 build。日志里应该看到:
No build cache found
Installing project dependencies: npm clean-install
added 487 packages in 32s
added N packages in Ns 比上次的 up to date in 1s 慢得多,证明真的重新装了。
Step 2:确认 package-lock.json 已提交且和 package.json 同步
# 本地强制重新生成 lockfile
rm -rf node_modules package-lock.json
npm install
# 确认进了 git 并已 push
git status package-lock.json
git add package-lock.json package.json
git commit -m "sync lockfile"
git push
.gitignore 检查:
grep -E "package-lock|yarn.lock|pnpm-lock" .gitignore
# 期望:无输出,或这些行被注释
只保留一种 lockfile。若用 pnpm 就删掉 package-lock.json 和 yarn.lock,保留 pnpm-lock.yaml。
Step 3:锁死 Node 版本并确认构建镜像
Pages 先读项目文件里的 Node 版本,再读 env var。按优先级:
# 方式 1:项目根 .nvmrc(推荐,本地和 CI 都生效)
echo "22.16.0" > .nvmrc
git add .nvmrc
# 方式 2:Pages dashboard 里设 NODE_VERSION 环境变量
NODE_VERSION = 22.16.0
注意:V3 构建系统里不会读 package.json 的 engines.node 字段来选 Node 版本,要用 .nvmrc / .node-version 或 NODE_VERSION。如果项目还停在旧镜像,去 Settings → Build → Build system version 切到 V3。下次 build 开头几行应该显示锁定的版本,和本地一致。
Step 4:确认安装/构建命令
通常你不需要改安装步骤——只要 lockfile 已提交,Pages 默认就跑 npm ci。你能控制的是构建命令和输出目录:
Build command: npm run build
Build output directory: dist
如果你希望安装失败时报得明明白白,可以把构建命令设成 npm ci && npm run build:npm ci 遇到 lockfile 不同步会直接报错,而不是默默用旧依赖。输出目录要跟框架对上(Astro/Vite 是 dist,CRA 是 build,Nuxt 是 .output/public 等)。
Step 5:验证产物和缓存行为
# 本地 build 一次,知道 hash 文件名应该长什么样
npm ci && npm run build
ls -la dist/
# 部署后抓一个 hash 资源,确认是新版本
curl -sI https://your-project.pages.dev/_astro/main.HASH.js
如果新部署上的 hash 文件名跟上次不一样,说明产物真的更新了。如果文件名一样,可能是输出目录被复用——回到 Step 1 再清一次缓存。
如何确认已修好
- 构建日志显示
No build cache found(或者Restoring from build cache之后跟着added N packages,而不是up to date in 1s)。 - 构建日志第一行显示你锁定的 Node 版本。
- 线上确实反映了改动——硬刷新(
Cmd/Ctrl+Shift+R)或curl一个 hash 资源,绕过 CDN 边缘缓存(它和构建缓存是两回事)。
预防建议
- 每次升级关键依赖(尤其是带原生 binding 的)后,在 dashboard 里清一次构建缓存。
- 始终把
package-lock.json和package.json放在同一个 commit 里提交并 push;Pages 跑的严格安装就依赖它。 - 用
.nvmrc锁 Node 版本(并把项目保持在 V3 构建镜像),避免默认 Node 升级带来意外。 - 仓库里只保留一种 lockfile(npm / yarn / pnpm 选一种)。
- 在 CI 里加一步校验 lockfile 是否最新:
npm install --package-lock-only && git diff --exit-code package-lock.json。
常见问题
构建缓存和 Cloudflare 的 CDN 缓存是一回事吗?
不是。构建缓存在 CI 构建环境里(包管理器缓存 + 框架构建产物);CDN 边缘缓存是把你部署好的资源发给访客的那层。构建陈旧要在 dashboard 里 Clear Cache;浏览器里页面陈旧要 purge CDN 或硬刷新。
我点了 Clear Cache,下一次 build 还是显示 up to date,为什么?
缓存是清了,但 Pages 按的那份 lockfile 还是旧的。清缓存并不会改你已提交的 lockfile。先做 Step 2(重新生成 + commit + push lockfile),再清缓存重部署。
之前的 “Builds & deployments” 标签去哪了?
被重新组织了。截至 2026 年 6 月,构建相关设置(构建命令、输出目录、构建缓存、构建系统版本)都在项目的 Settings → Build 下面。
为什么原生模块本地能跑,到 Pages 上就崩?
本地和 Pages 大概率是不同的 Node 大版本,缓存里的原生 binding 对不上。用 .nvmrc 锁成同一个 Node 版本,清构建缓存,重新部署让 binding 按正确运行时重新编译。
Cloudflare 跑的是 npm install 还是 npm ci?
只要 lockfile 已提交,Pages 跑的是严格安装(npm 即 npm ci)。这正是”提交一份最新 lockfile 才是真正修法”的原因——CI 里跑的根本不是宽松的 npm install。