Rucky 前端笔记

网络与安全

HTTP 缓存:讲清 Cache-Control

no-cache 并不是不缓存。搞懂强缓存和协商缓存,再记住“HTML 要验证,带 hash 的资源缓存一年”这条策略就够了。

两层缓存

浏览器拿到一个请求时,会依次判断:

  1. 强缓存:本地有副本且还在有效期内,直接使用,根本不发请求。
  2. 协商缓存:副本过期了,带上标识问服务器“内容变了吗”。没变就返回 304,没有响应体,继续用本地副本;变了就返回 200 和新内容。
HTTP
# 第一次响应
HTTP/1.1 200 OK
Cache-Control: max-age=3600
ETag: "abc123"

# 过期后再次请求
GET /app.css HTTP/1.1
If-None-Match: "abc123"

# 内容没变
HTTP/1.1 304 Not Modified

ETag 配 If-None-Match 是基于内容标识的验证,Last-Modified 配 If-Modified-Since 是基于修改时间的验证。两者都有时,优先看 ETag。

Cache-Control 指令

指令含义
max-age=N响应在 N 秒内是新鲜的,期间可以直接使用
no-cache可以存,但每次使用前都必须向服务器验证
no-store完全不存,用于敏感数据
publicCDN 等共享缓存也可以存
private只有用户自己的浏览器可以存
s-maxage=N只对共享缓存生效,优先于 max-age
immutable有效期内内容不会变,部分浏览器在刷新时也不再验证
stale-while-revalidate=N过期后 N 秒内可以先用旧内容,同时在后台更新

最容易混淆的就是 no-cache:它的意思不是“不缓存”,而是“用之前先问一下”。真正不缓存的是 no-store。

另一个常见误解:不写 Cache-Control 不等于不缓存。如果响应带有 Last-Modified,浏览器可以自己估算一个有效期,这叫启发式缓存,常常是“线上改了用户却看不到”的原因。

一条够用的策略

前端项目构建后,通常有带内容 hash 的静态资源(比如 app.3f2a1c.js)和入口 HTML。内容一变,文件名就变,所以可以这样配:

资源配置原因
HTML 入口no-cache每次验证,发布后用户马上能拿到新版本
带 hash 的 JS、CSS、图片public, max-age=31536000, immutable文件名变了才是新内容,可以放心缓存一年
不带 hash 的资源短 max-age 配 ETag无法靠文件名判断更新
个人数据接口private, no-cache 或 no-store不能让共享缓存存下用户数据

对应的 nginx 配置:

nginx
location = /index.html {
    add_header Cache-Control "no-cache";
}

location /assets/ {
    add_header Cache-Control "public, max-age=31536000, immutable";
}

几个实际会遇到的问题

  • **nginx 的 add_header 不会叠加继承。** 如果在 location 里写了 add_header,外层 server 里配置的响应头(比如安全相关的头)在这个 location 中就全部失效了,需要重新写一遍。
  • 刷新的行为不同。 普通刷新通常会重新验证页面本身;强制刷新(Ctrl/Cmd + Shift + R)会跳过缓存重新下载。调试时也可以在 DevTools 的 Network 面板勾选 Disable cache。
  • **别忘了 Vary。** 同一个地址可能根据请求头返回不同内容,比如压缩格式。Vary: Accept-Encoding 告诉缓存要把它们分开存,否则可能把压缩过的内容发给不支持的客户端。

怎么检查

打开 DevTools 的 Network 面板:

  • Size 列显示 disk cache 或 memory cache,说明命中了强缓存,没有发请求。
  • Status 为 304,说明走了协商缓存,发了请求但没有下载内容。
  • 点开请求,在 Response Headers 里确认 Cache-Control 是否符合预期。

延伸阅读