Cloudflare Pages 缓存优化实战

0 阅读6分钟

Cloudflare Pages 缓存命中率 7%?我用 3 条规则把它干到 90%

站点上线后国内访问慢成狗,LCP P75 高达 20 秒。 排查后发现 Cloudflare 缓存命中率只有 7.62%。 用 3 条 Cache Rules + 1 个本地化优化,命中率飙到 90%+,首屏从 20s 降到 3s。

问题发现

📚 本文是 UtlKit 技术系列第 9 篇系列索引 →

站点 utlkit.com 部署到 Cloudflare Pages 后,在国内访问非常慢。打开 Chrome DevTools 一看,LCP P75 高达 20 秒,P90 26 秒。

第一反应是"Cloudflare 在国内不是减速器吗",去后台看了 Cache Analytics——

缓存命中率 7.62%。

也就是说 92% 的请求都没有命中缓存,全部回源。Cloudflare Pages 的源站是边缘节点本身,但每次回源都要重新从存储层读取,TTFB 从几十毫秒变成几秒,加上国内网络环境,体验直接崩了。

根因分析

为什么缓存命中率这么低?

Cloudflare 默认对 HTML 页面不做缓存(或者说 Edge TTL 非常短),理由是动态网站需要实时内容。但对于纯静态站点来说,这个默认策略完全是反的。

具体原因有三:

1. Cloudflare 默认不缓存 HTML

Cloudflare 的设计哲学是"源站最懂自己的内容该缓存多久"。如果源站返回的 Cache-Control 头没有明确指示,CF 对 HTML 的 Edge TTL 只有 2 小时,而且很多情况下直接 bypass。

2. _headers 文件只能控制浏览器缓存

我在项目里配了 _headers(Cloudflare Pages 的响应头配置):

# out/_headers
/*
  Cache-Control: public, max-age=86400, stale-while-revalidate=604800

这个配置告诉浏览器"这个页面可以缓存 1 天"。但 Cloudflare 边缘节点自己认不认这个头?不一定。_headers 的输出是响应给浏览器的,对 CF 边缘缓存行为的直接影响有限。

3. Cookie 导致缓存 bypass

Cloudflare 默认行为:如果请求带了 Cookie,就 bypass 缓存。而浏览器访问自己的域名时,如果之前有过交互(比如点击、表单),就会带 Cookie。对于工具站来说,用户用了一个工具后访问另一个工具,Cookie 就带着了。

解决方案

方案选型

方案效果成本复杂度适用场景
Cache Rules★★★★免费所有站点
IP 优选 + Worker 代理★★★★★免费重度 CF 用户
国内 CDN 回源★★★★★¥50+/月有备案域名
只做 _headers★★免费极低辅助手段

结论:Cache Rules 是性价比最高的方案,先做这个。

第一步:创建 Cache Rules

去 Cloudflare Dashboard → Caching → Cache Rules → Create Rule。

免费套餐最多 3 条规则,我们正好 3 条。

规则 1:静态资源长缓存(优先级最高)

匹配条件:

HTTP Host equals "utlkit.com"
AND
HTTP Request URI matches one of:
  /_next/static/*
  /fonts/*

动作:

  • Edge TTL → Ignore cache-control header, 30 天
  • Browser TTL → Override, 30 天
  • Ignore query string ✅

Next.js 的 /_next/static/ 目录下所有文件都带了内容 hash,文件名变了就是新文件,所以可以大胆设长缓存。字体同理。

规则 2:sitemap 短缓存

匹配条件:

HTTP Host equals "utlkit.com"
AND
HTTP Request URI matches one of:
  /sitemap.xml
  /robots.txt

动作:

  • Edge TTL → Ignore cache-control, 1 天
  • Browser TTL → Override, 1 天
规则 3:HTML 页面兜底缓存(最关键)

匹配条件:

HTTP Host equals "utlkit.com"

动作:

  • Edge TTL → Ignore cache-control header, 14 天
  • Browser TTL → Override, 7 天
  • Ignore query string ✅(关键!/?cat=finance/ 共享缓存)

这里有个取舍:Edge TTL 设多长?太短了缓存没意义,太长了部署后用户看不到更新。 Cloudflare Pages 在每次部署后会自动 purge 缓存,所以 Edge TTL 可以设比较长。 浏览器 TTL 设短一些(7 天),防止用户清缓存前一直看旧版。

规则顺序: 静态资源 → sitemap → HTML(兜底放最后)

第二步:Purge Everything

配置完规则后,Caching → Configuration → Purge Everything,清除旧缓存。

第三步:验证

# 第一次请求
$ curl -sI https://utlkit.com/ | grep cf-cache-status
cf-cache-status: MISS    ← 正常,首次 MISS

# 第二次请求
$ curl -sI https://utlkit.com/ | grep cf-cache-status
cf-cache-status: HIT     ← 成功!

验证几个关键资源:

# 工具页
curl -sI https://utlkit.com/tools/bmi-calculator/ | grep cf-cache-status
# MISS → HIT ✅

# JS chunk
curl -sI https://utlkit.com/_next/static/chunks/main-app-xxx.js | grep cf-cache-status
# MISS → HIT ✅

# CSS
curl -sI https://utlkit.com/_next/static/css/xxx.css | grep cf-cache-status
# MISS → HIT ✅

额外优化:Sentry CDN 本地化

排查过程中还发现一个问题——layout.tsx 里引用了 Sentry 的 CDN:

<script src="https://browser.sentry-cdn.com/8.30.0/bundle.min.js" />

browser.sentry-cdn.com 在国内访问经常超时(有时完全连不上),这个 70KB 的脚本在 <head> 里,会阻塞整个页面渲染。

解决很简单:下载下来放到 public/ 目录:

curl -o public/sentry-bundle.min.js \
  https://browser.sentry-cdn.com/8.30.0/bundle.min.js

改引用:

- <script src="https://browser.sentry-cdn.com/8.30.0/bundle.min.js" />
+ <script src="/sentry-bundle.min.js" />

这样这个脚本走 Cloudflare 自己的 CDN,国内速度直接起飞。

⚠️ 注意:Sentry CDN bundle 更新时记得手动替换文件。可以设个提醒每季度检查一次。

效果对比

指标优化前优化后
Cloudflare 缓存命中率7.62%90%+
首页 TTFB(命中缓存)2-7s200-400ms
LCP P7520s2-3s
国内首屏加载经常 10s+稳定 3-5s

踩坑记录

坑 1:CF 新版界面和旧文档对不上

网上的教程大多是基于旧版 Cache Rules 界面写的,参数名称完全不同。新版界面(2024+)把很多设置拆到了子面板里,找不到 "Cache Level" 和 "Edge TTL" 的具体输入框。

解决方案: Edge TTL 现在是个下拉选项,选 "Ignore cache-control header and use this TTL" 后才能输入具体时间。最大可选 30 天。

坑 2:Ignore query string 不选,缓存被 query string 分裂

工具站的首页经常被访问为 /?cat=finance/?cat=developer 等。如果不忽略 query string,CF 会把每个不同的 query 当作不同的缓存 key。104 个工具 × 8 个分类 = 800+ 个缓存版本,命中率又掉了。

解决方案: 勾选 "Ignore query string",所有 query 共享同一份缓存。

坑 3:Browser TTL 设太长,部署后用户看不到更新

一开始 Browser TTL 也设了 14 天,结果部署了新版本,同事反馈"怎么还是旧的"。浏览器缓存不受 CF Purge 影响,只能等 TTL 过期。

解决方案: Browser TTL 降到 7 天,Edge TTL 保持 14 天。CF 部署自动 purge Edge 缓存,新访客立刻看到新版,老用户 7 天内逐步更新。

进阶方案

如果你的站点国内访问还是很慢(即使缓存命中了),可以考虑 IP 优选 + Worker 代理 方案:

  1. 在 DNS 中创建 cdn.yourdomain.com(灰云),CNAME 到第三方优选 IP 池
  2. 所有子域名灰云 CNAME 到 cdn.yourdomain.com
  3. Pages 本身不支持路由,用一个 Worker 做反向代理桥接
  4. Worker 走路由模式(支持优选),转发请求到 Pages

这个方案能把国内延迟从 300-500ms 压到 50-100ms,但维护成本也高得多。对于工具站来说,Cache Rules 优化后基本够用了。

总结

Cloudflare Pages 免费套餐 + 3 条 Cache Rules = 足够好的全球 CDN。

关键就三件事:

  1. 显式告诉 CF 缓存 HTML(默认不缓存)
  2. 忽略 query string(防止缓存分裂)
  3. 区分 Edge TTL 和 Browser TTL(Edge 设长,Browser 设短)

免费、简单、效果显著。


📚 本文是 UtlKit 技术系列第 9 篇系列索引 →


*本文基于 utlkit.com 实际优化经验。站点:utlkit.com