Cloudflare Pages 构建缓存陈旧:原因 + 修复路径

升级依赖后 CF Pages 还在发旧版本,因为它复用了陈旧的依赖缓存。提交 lockfile、清缓存、锁 Node。

升级了某个依赖,本地 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 datelockfile 陈旧/未提交,缓存被复用原因 1–2,Step 1–2
npm cilock file ... can only install with an existing package-lock.jsonout of synclockfile 缺失或不同步原因 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 statuspackage-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-sqlite3sharp 这类原生依赖在不同 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.jsonyarn.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.jsonengines.node 字段来选 Node 版本,要用 .nvmrc / .node-versionNODE_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 buildnpm 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 再清一次缓存。

如何确认已修好

  1. 构建日志显示 No build cache found(或者 Restoring from build cache 之后跟着 added N packages,而不是 up to date in 1s)。
  2. 构建日志第一行显示你锁定的 Node 版本。
  3. 线上确实反映了改动——硬刷新(Cmd/Ctrl+Shift+R)或 curl 一个 hash 资源,绕过 CDN 边缘缓存(它和构建缓存是两回事)。

预防建议

  • 每次升级关键依赖(尤其是带原生 binding 的)后,在 dashboard 里清一次构建缓存。
  • 始终把 package-lock.jsonpackage.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

相关阅读

标签: #部署 / 托管 #排查