目前的 RuoYi Vue 管理后台的前端发布之后,浏览器还是会缓存上一次发布的页面,触发手动刷新一下浏览器才能看到最新版本的前端页面。 目前使用的是 nginx 部署的。
修改前返回的 HTTP 头
HTTP/1.1 200 OK
Server: nginx/1.30.4
Date: Wed, 21 Oct 2026 07:29:39 GMT
Content-Type: text/html
Content-Length: 52778
Last-Modified: Mon, 19 Oct 2026 04:00:22 GMT
Connection: keep-alive
ETag: "6ad595d6-ce2a"
Accept-Ranges: bytes
禁止页面缓存
旧的 Nginx 配置:
location / {
root /var/www/sunzhongwei.com/dist;
try_files $uri $uri/ /index.html;
index index.html index.htm;
}
目前这个 location / 完全没有设置任何缓存头。nginx 对静态文件默认只下发 Last-Modified / ETag,浏览器会对 index.html 做”启发式缓存”(通常按 (当前时间 - Last-Modified) × 10% 来缓存),导致发版后浏览器仍然拿着旧的 index.html → 引用旧的 hash 资源名 → 页面不更新,必须手动刷新。
Vue 构建产物(app.8f3a2c.js、chunk-vendors.xxx.css)文件名本身带内容 hash,内容一变文件名就变,所以这些静态资源可以放心地长缓存;真正需要”每次都拿最新”的只有一个入口文件 index.html。
关键陷阱:SPA 的 try_files $uri $uri/ /index.html 回退时,浏览器请求的是根路径 /,匹配的是 location / 本身,URI 并不是 *.html,所以很多人写的 location ~* \.html$ { ... } 根本拦不到根路径回退。最稳妥的办法是按响应 Content-Type 来区分缓存策略。
需要两处改动:
① map 必须写在 http 上下文(nginx.conf 主配置或 conf.d/ 下被 http { ... } include 的文件里,不能写在 server 块内):
# http 上下文:按响应类型设置缓存策略
map $sent_http_content_type $cache_control {
default "public, max-age=31536000, immutable"; # 带 hash 的 js/css/img 等长缓存
~text/html "no-cache, no-store, must-revalidate"; # 入口 HTML 每次都重新校验
}
② 在现有 server 的 location / 里加一行 add_header:
location / {
root /var/www/sunzhongwei.com/dist;
try_files $uri $uri/ /index.html;
index index.html index.htm;
# 入口 HTML 不缓存(含 SPA 根路径回退),静态资源长缓存
add_header Cache-Control $cache_control;
}
这样:
- 访问 /(回退到 index.html,Content-Type 是 text/html)→ Cache-Control: no-cache, no-store, must-revalidate,浏览器每次都向服务端校验,拿到最新的 index.html;
- 访问 /js/app.8f3a2c.js(Content-Type 是 application/javascript)→ Cache-Control: public, max-age=31536000, immutable,一年内直接命中本地缓存,无需重新下载。
因为新发布后 index.html 一定是最新的,它引用的就是新 hash 资源名,刷新即生效。
改完配置后 nginx -t && nginx -s reload
修改后返回的 HTTP 头
HTTP/1.1 200 OK
Server: nginx/1.30.4
Date: Wed, 21 Oct 2026 07:35:25 GMT
Content-Type: text/html
Content-Length: 52778
Last-Modified: Mon, 19 Oct 2026 04:00:22 GMT
Connection: keep-alive
ETag: "6ad595d6-ce2a"
Cache-Control: no-cache, no-store, must-revalidate
Accept-Ranges: bytes
果然 OK 了。