协商缓存起效需ETag与Last-Modified协同工作,服务端生成准确标识、浏览器正确携带请求头、中间层不破坏三者缺一不可;ETag应基于文件内容(如SHA-256)生成以规避Last-Modified精度缺陷。

协商缓存要真正起效,关键不是单独设 ETag 或 Last-Modified,而是让两者协同工作、各司其职,并避开常见干扰。服务端生成准确标识 + 浏览器正确携带请求头 + 中间层不破坏,三者缺一不可。
ETag 优先用于内容敏感场景
ETag 应基于文件内容生成(如 SHA-256 哈希),而非随机值或时间戳。内容变则 ETag 变,内容不变则 ETag 固定——这能规避 Last-Modified 的秒级精度缺陷和部署时时间戳批量覆盖问题。
- 小文件(crypto.createHash('sha256').update(data).digest('hex')
- 大文件避免全量读取,可分块哈希或改用
fs.statSync().ino + fs.statSync().size组合(仅作弱校验,不替代内容哈希) - 设置响应头:
res.setHeader('ETag', `"${hash}"`),注意包裹双引号,符合 HTTP 规范
Last-Modified 作为辅助兜底机制
Last-Modified 不是可选项,而是 ETag 的有效补充。当 ETag 因性能原因未启用,或 CDN/代理层剥离了 ETag 时,Last-Modified 仍能维持基础协商能力。
- 务必用
mtime.toUTCString()格式化,例如Wed, 18 Dec 2013 00:32:38 GMT - 不要用
new Date(mtime).toUTCString()二次解析,避免时区或毫秒截断误差 - 同时设置
Cache-Control: no-cache,确保浏览器跳过强缓存,主动发起协商请求
服务端比对逻辑必须严格且简洁
浏览器第二次请求会自动带上 If-None-Match(对应 ETag)和 If-Modified-Since(对应 Last-Modified)。服务端应按顺序判断,任一命中即返回 304。
立即学习“Java免费学习笔记(深入)”;
- 先检查
If-None-Match是否等于当前 ETag(字符串全等,含引号) - 再检查
If-Modified-Since是否严格等于当前mtime.toUTCString() - 两个都不匹配才返回 200 + 新内容 + 更新后的 ETag 和 Last-Modified
- 返回 304 时清空响应体:
res.statusCode = 304; res.end();
绕过中间层干扰的实操要点
Webpack Dev Server、Nginx、CDN 等常默认不透传或覆盖 ETag/Last-Modified,需显式配置。
- Webpack Dev Server:启用
devServer.headers或使用webpack-plugin-serve插件注入 - Nginx:关闭
etag off;,并确认未设置add_header Last-Modified "";这类覆盖指令 - CDN(如 Cloudflare):检查缓存规则是否启用「尊重源站头」,禁用「自动重写 ETag」选项


















