内容哈希通过让文件名随内容变化实现精准缓存更新,Webpack需用[contenthash]配合MiniCssExtractPlugin和HtmlWebpackPlugin,Vite需显式配置assetFileNames与entryFileNames,并确保HTML自动注入、Nginx对带哈希CSS设immutable。

内容哈希不是“加个参数就能刷新”,而是让 CSS 文件名本身随内容变化——浏览器看到 main.a1b2c3.css 和 main.d4e5f6.css,天然当作两个资源,旧缓存完全不干扰新文件加载。
Webpack 中必须用 [contenthash],别用 [hash] 或 [chunkhash]
Webpack 有三种哈希占位符,只有 [contenthash] 能保证“内容不变,哈希不变;内容一变,哈希必变”。[hash] 是整个构建的总哈希,改一行 JS 就让所有 CSS 文件名全变;[chunkhash] 在 Webpack 4+ 已被弃用,且对 CSS 提取不稳定。
- 配置示例(
webpack.config.js):module.exports = { output: { filename: 'js/[name].[contenthash:8].js', chunkFilename: 'js/[name].[contenthash:8].js' }, plugins: [ new MiniCssExtractPlugin({ filename: 'css/[name].[contenthash:8].css', chunkFilename: 'css/[name].[contenthash:8].css' }) ] }; - 必须搭配
MiniCssExtractPlugin:它把 CSS 从 JS 中抽出来成独立文件,才能给 CSS 单独打哈希;用style-loader会把样式塞进 JS,CSS 根本没独立文件名 - 检查构建产物:确认生成的 CSS 文件名里真有 8 位哈希,且相同内容多次构建哈希一致
Vite 项目要显式配 assetFileNames,默认不给 CSS 加哈希
Vite 默认只对 JS 文件用 [hash],CSS 文件名仍是 style.css。生产构建后如果 CDN 缓存了这个无哈希路径,更新就彻底失效。
- 在
vite.config.ts中强制覆盖 CSS 输出命名:build: { rollupOptions: { output: { assetFileNames: 'assets/[name].[hash].css', entryFileNames: 'assets/[name].[hash].js', chunkFileNames: 'assets/[name].[hash].js' } } } -
[hash]在 Rollup 里等价于 Webpack 的[contenthash],是内容级哈希 - 若启用了
cssCodeSplit: true(默认开启),必须同时配entryFileNames和chunkFileNames,否则动态 import 的 CSS chunk 仍可能无哈希
HTML 中的 <link> 必须由构建工具自动注入,不能手写
哪怕构建出了 main.a1b2c3.css,如果你在 index.html 里硬写 <link href="main.css">,浏览器永远请求旧路径,404 或加载旧缓存。
立即学习“前端免费学习笔记(深入)”;
- Webpack 用户:确保
HtmlWebpackPlugin开启inject: true(默认就是),它会扫描 JS 中的import './main.css',再自动替换 HTML 中的<link> - Vite 用户:默认启用 HTML 插件,但前提是你的 CSS 是通过
import引入的(如main.ts里import './style.css'),而不是在 HTML 里手动写<link> - 检查最终 HTML:打开构建后的
index.html,确认<link href="assets/main.a1b2c3.css">是自动生成的,不是你手写的
Nginx 需为带哈希的 CSS 配 immutable,否则 Chrome 不跳过验证请求
光有哈希文件名还不够。如果 Nginx 对 .css 统一返回 Cache-Control: max-age=3600,Chrome 在第二次访问时仍会发条件请求(带 If-None-Match),多一次网络往返。
- 在 Nginx 配置中按后缀精准控制:
location ~* \.css$ { expires 1y; add_header Cache-Control "public, immutable"; } -
immutable告诉 Chrome:“这文件一年内绝不会变”,它连 ETag 都不发,直接复用本地副本 - 务必验证响应头:用
curl -I https://yoursite.com/assets/main.a1b2c3.css,确认返回的是Cache-Control: public, immutable,而不是max-age=3600或其他值 - 如果用了 CDN(如 Cloudflare),确保它没覆盖源站响应头,或在 CDN 控制台把
.css缓存规则设为“忽略查询参数、按完整 URL 缓存”
最容易被忽略的是:构建产物里确实生成了带哈希的 CSS,但 HTML 没更新引用,或者 Nginx 把所有 .css 都按同一套缓存头下发——这两处一错,哈希就白打了。


















