灰度发布必须由服务端或边缘网关在html返回前完成,因浏览器强缓存index.html会导致html与js/css版本错配;nginx map+sub_filter可基于cookie动态替换资源路径,需静态资源物理隔离且路径带版本。

灰度发布不能靠改 HTML 文件本身实现,它必须在 HTML 被浏览器拿到之前,由服务端或边缘网关决定返回哪一版内容。
为什么不能直接替换 index.html 文件
浏览器对 index.html 有强缓存倾向(尤其是设置了 Cache-Control: public, max-age=3600 时),用户可能长期加载旧版 HTML,而新版 JS/CSS 已上线——结果是 HTML 加载旧逻辑,却请求了新资源路径,或反过来,造成功能错乱、404、白屏。
- HTML 缓存策略难以精细控制:你没法按用户维度缓存不同版本的
index.html - CDN 会把同一 URL 的响应泛化分发,无法识别 cookie 或 header 中的灰度标识
-
前端 JS 在 HTML 解析后才执行,此时已错过静态资源加载时机——
<script src="/app.js"></script>这类标签早已发出请求
Nginx map + sub_filter 是最轻量的落地方式
不用改业务代码,不依赖 Lua,靠 Nginx 原生指令就能完成 HTML 内资源路径的实时注入。关键在于两点:提前约定占位符、用 sub_filter 替换。
- HTML 模板里写
<script src="/static/%7BVERSION%7D/app.js"></script>,不是硬编码v1或v2 - 用
map $cookie_gray_version $static_prefix把gray_version=v2映射为/static/v2 - 在
location /块中启用sub_filter '{VERSION}' $static_prefix,并设置sub_filter_once off - 务必加
sub_filter_types text/html,否则默认只处理text/plain
这样,同一个 index.html 文件,对不同用户返回的内容实际不同:一个含 /static/v1/app.js,另一个含 /static/v2/app.js。
静态资源必须物理隔离存放
灰度成败取决于资源路径是否天然隔离。如果 /static/app.js 同时被 v1 和 v2 版本复用,CDN 缓存就会混用,导致版本错配。
- 构建产物应输出到带语义版本或哈希的子目录,例如
/static/v2.5.0/、/static/abc123/ - Nginx 的
alias /path/to/static/$static_prefix/必须指向具体版本目录,不能 fallback 到通用路径 - 所有资源(JS/CSS/图片/字体)都走同一前缀,避免部分资源漏切
- 不要用 query 参数做版本区分(如
app.js?v=v2),CDN 多数默认忽略 query 做缓存键
Service Worker 不能替代服务端灰度决策
SW 可以辅助灰度运行时行为(比如缓存隔离、降级重试),但它读不到初始请求的 cookie 或 header——因为 SW 的 fetch 事件发生在 HTML 加载之后,且无法干预 HTML 本身的返回内容。
-
navigator.serviceWorker.register('/sw-v2.5.0.js')这行代码必须由服务端注入 HTML,不能靠 JS 动态判断注册 - SW 的
caches.open()缓存名建议带版本,如caches.open('static-v2.5.0'),但前提是 HTML 已明确告知当前版本 - 若在 SW 中尝试读取
document.cookie或localStorage来决定加载哪个资源,会失败:SW 线程无document对象;且 localStorage 在 fetch 阶段不可访问
真正容易被忽略的是:灰度的起点永远在第一个 HTTP 请求头到达网关的那一刻,而不是页面 JS 执行的那一秒。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











