React+Docker+Nginx容器重建后首次需硬刷新才可正常访问问题
问题根因
报错Uncaught SyntaxError: expected expression, got '<'的直接原因是:浏览器请求JS资源时,服务端返回的不是JS代码,是<!DOCTYPE html>开头的HTML文档,JS引擎解析到第一个字符<就抛出语法错误。
触发链路完全匹配描述的现象:
- React生产构建会给JS/CSS等静态资源拼接内容hash,比如旧版本入口文件是
main.5a72dfd2.js,新版本构建后入口hash变化,文件名变成全新的,旧文件在新容器里已经不存在。 - 重建容器后首次普通刷新时,浏览器本地HTTP缓存中还留存着旧版本的
index.html,这个旧文件里引用的还是旧hash的JS路径。浏览器按旧路径请求JS资源时,当前服务端已经找不到对应旧文件,而SPA应用默认配置了路由兜底:所有匹配不到静态资源的请求都会返回index.html,最终导致JS请求拿到了HTML内容。 - 硬刷新会强制浏览器忽略本地缓存,重新拉取最新版本的
index.html,拿到新的JS资源路径后请求正常,后续普通刷新使用本地已更新的index.html,自然不会再报错。
当前Nginx配置存在两个问题放大了这个故障:
- 没有单独处理静态资源请求,所有路径全部走根路由兜底,导致不存在的旧JS请求不会返回404,而是错误返回HTML。
- 强行给所有响应添加
add_header Last-Modified $date_gmt;,会覆盖后端服务返回的真实文件修改时间,干扰浏览器的缓存判断逻辑,增加了旧版index.html被异常缓存的概率。同时给所有响应统一配置no-store, no-cache,完全浪费了带hash静态资源可安全长缓存的特性,也没有从根本上解决旧资源请求返回HTML的问题。
修复方案
调整Nginx配置,分路径处理缓存规则和错误响应,操作如下:
- 新增静态资源专属匹配规则,优先拦截所有带静态后缀的请求,资源不存在时直接返回404,禁止走index.html兜底,同时给带hash的静态资源配置长缓存提升加载性能。配置片段如下:
# 优先匹配静态资源请求 location ~* \.(?:js|css|png|jpg|jpeg|gif|svg|ico|webp|woff|woff2|ttf|eot)$ { proxy_pass http://app:4000; # 开启代理错误拦截,后端返回404时直接透传404状态码 proxy_intercept_errors on; error_page 404 =404; # 带内容hash的静态资源内容永远不变,可配置1年长缓存 add_header Cache-Control "public, max-age=31536000, immutable"; }
- 调整原有根路由配置,移除错误的固定Last-Modified头,删掉原有
if_modified_since off;、expires off;、etag off;配置,仅给HTML入口文件配置协商缓存规则,确保浏览器每次都能校验到最新的index.html:
location / { proxy_pass http://app:4000/; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; # HTML入口文件禁止强缓存,每次请求都校验更新 add_header Cache-Control "no-cache, must-revalidate"; expires -1; etag on; if_modified_since on; }
- 重载Nginx配置生效即可。后续版本更新重建容器后,浏览器请求旧hash的JS资源时会直接收到404响应,会自动重新拉取最新的index.html加载新资源,无需手动硬刷新。
内容的提问来源于stack exchange,提问作者Damn devil
相关产品推荐
相关产品推荐

