所有文章

Engineering / 开发手记排版示例

给静态资源一个恰到好处的缓存策略

从一次「上线了,但没有完全上线」说起。记录浏览器与 CDN 之间的缓存边界,以及一套简单、可预测的静态资源发布方式。

本文目录 · 4 个章节

为什么改完代码,页面却没变?

一次很小的样式更新,构建正常,部署正常,本地验证也正常。但打开线上页面,按钮还是原来的样子。刷新几次没有变化,直到清空浏览器缓存,一切才终于对上。

问题不在 CSS,而在资源的生命周期:文件内容更新了,URL 没变,浏览器仍然认为手里的副本是新鲜的。于是,一次发布是否生效,变成了用户有没有碰巧刷新缓存。

先把缓存的边界画清楚

一次资源请求,通常会依次经过浏览器缓存、CDN 边缘节点,最后才到源站。每一层都可能直接返回副本,也都需要一套清晰的更新约定。

图 01 · 前一层无法满足请求时,才继续向后查找。

这里先聚焦两类文件:作为入口的 HTML,以及由 HTML 引用的脚本、样式和图片。入口负责告诉浏览器「当前版本是什么」,其余资源负责提供这一版本的具体内容。

资源类型建议策略更新方式
HTML 入口no-cache使用前重新验证
带指纹的 JS / CSS长期缓存 + immutable内容变化时生成新 URL
固定地址的图片短期缓存或重新验证按实际更新频率调整

no-cache 并不等于不允许存储。它允许保存副本,但再次使用前需要验证;资源未变时,服务端可以返回 304,省去重复传输响应体。

让文件名承担版本管理

为构建产物加入内容哈希,是最直接的一步。比如 app.8f31c2.js:只要内容不变,URL 就不变;内容一旦变化,就生成新的地址。

把长期缓存限定在带指纹的目录

下面的 Nginx 片段放在现有 server 配置中。约定 /assets/ 只包含构建生成、带内容指纹的文件;固定文件名的资源需要单独配置。

Code
# HTML 入口:每次使用前重新验证
location = /index.html {
    add_header Cache-Control "no-cache";
}

# 仅用于带内容指纹的构建资源
location /assets/ {
    try_files $uri =404;
    add_header Cache-Control "public, max-age=31536000, immutable";
}

发布时先上传新资源,再更新 HTML 入口。旧资源保留一段时间,让仍打开旧页面的用户可以继续访问对应的文件,也为回滚留出余地。

发布前,再检查一遍

验证缓存策略,不需要依赖「多刷新几次」。打开开发者工具,观察 Network 面板中的响应头和资源地址,就能确认大部分问题。

  1. 检查入口:确认 HTML 的 Cache-Control 与预期一致,重新验证机制正常。
  2. 检查产物:修改一行代码后重新构建,确认对应文件的哈希发生变化。
  3. 检查发布顺序:新入口引用的文件都已上传,旧版本资源仍然可用。
  4. 模拟旧页面:保留一个旧标签页,在新版本发布后继续操作,观察是否出现资源 404。

缓存的目标,是让重复请求更便宜,也让更新可靠到达。把入口和资源分开管理之后,「用户看到哪个版本」就有了明确的答案。

延伸阅读

END OF NOTE
KEEP READING / 继续阅读

把周末还给山野