html 文件不应加内容哈希后缀,必须用 no-cache + etag 协商缓存;js/css 等静态资源需加 contenthash 并配 immutable 强缓存,etag 须基于文件内容生成,二者必须成对出现。

HTML 文件本身不能也不该加内容哈希后缀——这是前端缓存体系里最常被误操作的起点。真正要加哈希、配强缓存、做指纹校验的,是它引用的 JS、CSS、woff2、png 这类静态资源;而 HTML 必须走协商缓存,靠 ETag + no-cache 保证每次都能拿到最新调度入口。
为什么给 index.html 加 contenthash 后缀会出事
常见错误现象:index.abc123.html 部署后页面白屏、相对路径全 404、PWA 的 manifest.json 和 service-worker.js 中预缓存列表失效、SEO 收录断链。
根本原因有三:
- HTML 是入口文档,浏览器始终请求固定路径
/index.html;改名就得同步改 Nginx 路由、CDN 回源规则、所有硬编码路径,维护成本指数级上升 - HTML 内容本身高频变动(标题、meta、内联脚本、动态
script src),但它的作用只是“调度器”,不是被缓存的对象 - 即使你把
app.d41d8c.js的哈希写进 HTML,只要 HTML 自己被强缓存卡住,用户就永远看不到这个新 URL —— 缓存链条断在第一环
HTML 必须用 no-cache + ETag 协商缓存
这不是妥协,而是设计使然:既要避免用户看到陈旧 HTML,又不能每次重传整文件浪费带宽。
Nginx 配置关键点:
-
add_header Cache-Control "no-cache, must-revalidate";—— 强制每次发条件请求,不等于“不缓存” -
add_header ETag "";—— 让 Nginx 基于文件内容自动生成 ETag(推荐 md5) -
expires epoch;—— 清除 Expires 头,避免与 Cache-Control 冲突
验证是否生效:DevTools → Network → 刷新页面 → 找到 index.html 请求 → Response Headers 里应有 ETag 字段,且第二次请求状态码为 304 Not Modified。
JS/CSS 等静态资源必须加 contenthash 并配 immutable
Webpack 示例配置:output.filename: "[name].[contenthash:8].js";Vite 默认开启 build.rollupOptions.output.entryFileNames 带 hash。
服务端强缓存必须配合 immutable:
location ~* \.(js|css|woff2|png|jpg|gif)$ { expires 1y; add_header Cache-Control "public, immutable"; }-
immutable的作用是告诉浏览器:“这个 URL 永远不会变内容”,后续访问跳过If-None-Match校验,省一次往返 - ⚠️ 如果没加哈希就配
immutable,改了文件内容但 URL 不变,用户将永久卡在旧版本
客户端指纹校验只存哈希值,不存资源内容
LocalStorage 或 IndexedDB 只适合存轻量指纹映射,比如从构建生成的 manifest.json:
{"main.js": "d41d8cd98f00b204e9800998ecf8427e", "style.css": "5d41402abc4b2a76b9719d911017c592"}
加载前比对逻辑应满足:
- 读
localStorage.getItem('assetFingerprints')获取当前哈希快照 - 对比远程
manifest.json中对应资源的哈希值,不一致则强制更新该资源 URL(如加时间戳或换新哈希路径) - 绝不把 JS/CSS 内容直接塞进 LocalStorage —— 有 5–10MB 限制、阻塞主线程、无压缩
- 必须设兜底机制:比如每次发布带
buildId,发现不一致就清空旧指纹,避免哈希错乱导致静默降级
真正容易被忽略的是:ETag 的生成必须基于文件内容(而非修改时间),且 immutable 和 contenthash 必须成对出现——漏掉任一环,整个缓存更新链条就断了。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











