域名走 Cloudflare 优选后,怎么配缓存规则

域名走 Cloudflare 优选后,怎么配缓存规则

记录一次 Cloudflare Pages 静态博客在优选 CNAME 后的缓存规则配置。

我开始的想法

我的博客是 Nuxt 生成的静态站,部署在 Cloudflare Pages。为了让国内访问好一点,域名又套了一层 Cloudflare 优选 CNAME。

最后的链路大概是这样:

txt
访客
-> blog.zery.cn
-> 优选 CNAME
-> Cloudflare Pages
-> 静态文件

然后我发现个问题:

优选只是在选好线路,该加载的资源不会变。

最终访问 blog.zery.cn 时,除了路线,网站本应加载的资源对速度也有一定影响,特别是大头文件,正确规划他们的缓存规则就很重要了, 边缘缓存浏览器缓存...

我一开始想偷懒:全站直接 /* 缓一年,多省事。后来发现 Nuxt 静态站大部分博客都是不能这么粗暴,尤其是 _nuxt 下面那些带 hash 的资源和普通 HTML 不是一类东西。

缓存

当时的心理活动:都静态博客了,给我缓存一年怎么了?

不过话说回来,缓存对速度的帮助确实很明显,不是那种“心理优化”。最直观的例子就是刷新当前页面:有缓存时,浏览器能直接用本地缓存,或者很快从离自己近的 CDN 节点拿到结果,体感就是几乎秒开;没有缓存时,就要重新走一遍请求链路,HTML、payload、CSS、JS 都得重新拉,慢的时候差距非常明显。

三类缓存文件

Nuxt 静态博客生成出来后,常见资源大概分三类。

第一类是页面和数据,比如首页、文章页、归档页、_payload.json。这些内容会跟着发文章变化,适合短缓存。

第二类是 _nuxt 里的 JS/CSS。它们通常文件名带 hash,内容变了文件名也会变。这类资源适合长缓存,Cloudflare Pages 默认就会给比较激进的缓存头1年。

第三类是预览页、后台接口一类的路径。如果内容可能随时变,或者不想被中间层存住,就应该加 no-store

txt
HTML / payload       短缓存
/_nuxt/*             让 Nuxt / Pages 默认处理
/preview*            不缓存
字体、图片转换结果    长一点缓存

最好不要直接写全站 /*

我踩过的坑是:Cloudflare Pages 的 _headers 不是“最后命中一条就结束”。多个规则同时匹配时,响应头可能会合并。

如果你写:

public/_headerstxt
/*
  Cache-Control: public, max-age=300, s-maxage=1800

/_nuxt/*
  Cache-Control: public, max-age=31536000, immutable

访问 /_nuxt/entry.xxx.css 时,它既命中 /*,也命中 /_nuxt/*。结果就可能变成两个 Cache-Control 混在一起:

http
Cache-Control: public, max-age=300, s-maxage=1800, public, max-age=31536000, immutable

这就很尴尬了。你本来想让 _nuxt 走一年强缓存,结果短缓存也混进来了。到底哪个生效、不同中间层怎么理解,都不如一开始就别让它混在一起。

我最后用的规则

我的文章路径是 /2026/hello-world 这种年份开头,所以用 /20* 来覆盖文章页和文章 payload。它不会碰到 /_nuxt/*,维护起来也比每篇文章手写路径舒服。

这里的 ! Cache-Control 是为了先移除已有的同名响应头,再写自己的值。这样可以减少重复 header 的合并。

缓存头的每个值

短缓存这一行:

http
Cache-Control: public, max-age=300, s-maxage=1800, stale-while-revalidate=86400

我的理解是:

  • max-age=300:浏览器本地缓存 5 分钟,文章改了不至于在读者电脑里卡太久。
  • s-maxage=1800:共享缓存,比如 CDN,缓存 30 分钟。
  • stale-while-revalidate=86400:缓存过期后,可以先返回旧内容,同时后台去更新。

这套配置比较适合静态博客:平时访问能吃到 CDN 缓存,发文章后如果很急,可以手动清一下缓存;不急的话,等一会儿也会慢慢刷新。

长缓存这一行:

http
Cache-Control: public, max-age=31536000, immutable

只适合文件名会变的资源,比如字体、带 hash 的静态资源。别给 HTML 这么写,不然你改了文章,读者浏览器可能还在看很久以前的页面。

自己测一下

不要只看后台规则有没有保存。最终以线上域名返回的响应头为准。

PS>
curl.exe -I https://blog.zery.cn/
PS>
curl.exe -I https://blog.zery.cn/2026/hello-world
PS>
curl.exe -I https://blog.zery.cn/archive/_payload.json
PS>
curl.exe -I https://blog.zery.cn/_nuxt/entry.CgjSPprJ.css
PS>
curl.exe -I https://blog.zery.cn/preview

正常情况下,我希望看到类似这样:

txt
/                         public, max-age=300, s-maxage=1800, stale-while-revalidate=86400
/2026/hello-world          public, max-age=300, s-maxage=1800, stale-while-revalidate=86400
/archive/_payload.json     public, max-age=300, s-maxage=1800, stale-while-revalidate=86400
/_nuxt/entry.xxx.css       public, max-age=31536000, immutable
/preview                   no-store

如果 _nuxt 的 CSS / JS 也变成了 max-age=300,基本就是规则写宽了。回去检查有没有 /*/:year/:slug 这种把 _nuxt 一起扫进去的规则。

CF-Cache-StatusAge 可以顺手看,但别太相信第一次结果。刚部署完、刚清缓存、或者某个边缘节点第一次访问,MISSAge: 0 都很正常。多请求几次,或者换一个页面再看,才比较接近真实状态。

在哪里配置

如果是 Cloudflare Pages 静态站,我更愿意把这些规则放在仓库的 public/_headers

原因很简单:它跟着代码走。换机器、重新部署,都不会忘记自己当时到底在后台点了什么。

我也试过直接在 Cloudflare 控制台的 Cache Rules 里配,但这条链路下不稳定,至少我这里没生效。原因是 blog.zery.cn 前面走了优选 CNAME,DNS 记录本身不是普通小黄云代理入口;Cache Rules 要在“这次请求真正经过的那个 Cloudflare 代理层”执行,才会改到最终响应。

所以我最后的判断标准很简单:后台缓存规则保存成功了不算,curl -I https://blog.zery.cn/... 看到 Cache-Control 真的变了才算数。既然我控制台规则试下来没改动响应头,就把缓存规则写到 Pages 输出的 _headers 里,让cf能get到我的缓存规则。

当然,如果你前面还有一层别的 CDN 或优选服务,它也可能有自己的缓存规则。我的习惯是先让看看源站响应头怎么缓存,看前面的 CDN 对它有没有影响。

更新文章后怎么办

这套规则不是“永远不刷新”,而是把刷新节奏压到一个能接受的范围。

平时发文章,我一般这样处理:

  1. 提交文章,等 Cloudflare Pages 重新部署。
  2. 线上用 curl -I 看首页、文章页、payload 的 Cache-Control
  3. 如果只是普通更新,等短缓存自然过期。
  4. 如果是错别字、链接错误、重要内容,直接去 CDN 后台清缓存。

优选域名只是让访问路线更顺一点,缓存规则才是让第二次、第三次访问轻一点。两者不是互相替代的关系,各行其责,让网站访问体验更好。

今天把 Open WebUI 的文件残留清干净了
Twikoo 评论 IP 属地不对,把后端改成 IPv4-only

评论区

评论加载中...