概述
永远不会变化的 URL 无法长期缓存,因为它背后的内容确实会变化。内容哈希把 URL 变成字节的函数:文件变化时文件名随之变化,旧 URL 可以永久缓存。于是 HTML 成为唯一需要每次校验的文档。正是这种拆分,让部署对用户即时生效,同时允许静态资源缓存一年。
步骤
- 让构建工具为每个资源生成带哈希的文件名:
export default {
build: {
rollupOptions: {
output: {
entryFileNames: 'assets/[name].[hash].js',
assetFileNames: 'assets/[name].[hash][extname]',
},
},
},
};
- 为哈希资源与 HTML 设置不同的缓存策略:
/assets/*
Cache-Control: public, max-age=31536000, immutable
/index.html
Cache-Control: no-cache
- 验证边缘实际返回的响应头:
curl -sI https://app.example.com/assets/app.9f2c1a.js | grep -i cache-control
curl -sI https://app.example.com/ | grep -i cache-control
- 校验 HTML 引用的文件都存在于本次部署:
grep -o '/assets/[^"]*' dist/index.html | while read -r f; do
test -f "dist$f" || echo "missing $f"
done
验证
哈希资源应返回 max-age=31536000, immutable,HTML 应返回 no-cache。构建后 HTML 中的每个引用都应存在于 dist,否则构建失败而不是上线一个缺样式的页面。发布新版本后强制刷新应看到新的哈希文件名,而旧文件在缓存窗口内仍可访问,保证仍持有旧 HTML 的浏览器不会 404。
注意事项
- 删除旧哈希文件会让回滚和尚未更新的浏览器失效,应保留至少一个缓存周期。
- 不要在同时包含非哈希文件的路径前缀上应用 immutable。
- Service Worker 拥有独立缓存,需要与 CDN 一起版本化。
- 先上传资源再上传 HTML,避免用户拿到引用不存在文件的页面。
- 回滚时应重新部署旧 HTML,而不是假设旧资源仍然存在。