前端多版本零404部署实践:为什么会404,以及怎么彻底解决

这是一篇给"小白也能看懂"的实践文:讲清现象、根因、方案选择与我们的落地实现。


1. 现象:为什么发布新版本后会出现 404?

一个真实场景:

  • 10:00 用户打开了你的网页(加载的是 v1.0.4 的 HTML)
  • 10:10 你发布了 v1.0.5
  • 用户没有刷新页面,继续点击某个功能
  • 页面尝试按旧 HTML 里的地址加载某个 chunk:/assets/pages-about-about.DK5VADjQ.js
  • 服务器上只剩 v1.0.5 的文件,旧的被删了 → 直接 404

关键点:

  • HTML 决定了要加载哪些 JS/CSS(包含具体 hash 文件名)
  • 只要用户手上是旧 HTML,就会请求旧版 hash 的文件
  • 如果服务器把旧文件删了,用户就会 404

2. 根因:不是缓存,而是"源站有没有旧文件"

很多文章只讲"内容哈希 + immutable 缓存",默认隐含"旧文件在源站/存储里还存在"。

  • 有缓存:命中本地/中间缓存,自然不 404
  • 没缓存:会去源站拉,如果源站还保留旧文件,也不会 404
  • 真正的 404 出在"把旧资源从源站删了",而不是"浏览器没缓存"

结论:避免 404 的关键不是浏览器缓存,而是"源站/存储上保留旧文件一段时间"。


3. 方案总览(从易到难)

  • A. 接受小概率 404 → 弹窗提示"有新版本,请刷新"
    • 简单,易落地;体验打断、不优雅
  • B. Service Worker 强制刷新
    • 简化实现;用户会被强制 reload,可能丢失上下文
  • C. 不删旧资源(推荐)
    • 源站/存储保留最近 N 个版本的资源;HTML 短缓存
    • 旧 HTML 始终能拿到对应 js/css,零 404,体验最佳
  • D. 网关/Node 层按 manifest 做 chunk 兜底
    • 复杂度高;适合更重的后端网关治理

本文主讲 C 方案。


4. 我们的落地:/assets 聚合 + /versions 备份 + HTML 短缓存

目标:

  • 页面永远从 /assets 加载资源(统一入口,不改路径)
  • 保留最近 3~5 个版本的所有资源在 /assets,旧 HTML 永远能命中
  • 每次发布只切换入口 HTML(dist/current),不动 /assets

目录结构:

复制代码
dist/
├─ current/                # 当前入口(index.html、manifest.json 等)
├─ assets/                 # 资源聚合池(最近N个版本的 js/css/png/...)
└─ versions/               # 版本档案(每次发布的完整备份)
   ├─ v1.0.5/
   │   ├─ index.html
   │   └─ assets/*
   ├─ v1.0.4/
   └─ ...

发布流程(关键规则):

  1. 先 build(产物在 dist/build/h5
  2. deploy: 将 dist/build/h5 复制到 dist/current(并写入 manifest.json
  3. 同步最近 N 个版本的 assets/*dist/assets(追加,不覆盖已存在同名 hash 文件)
  4. 清理 dist/assets 中"超出 N 版本范围"的多余文件(可控)

为什么这样零 404:

  • 旧 HTML 会请求旧 hash 文件 → dist/assets 中还在 → 命中成功
  • 新 HTML 请求新 hash → 也在 → 命中成功
  • 版本过渡期内(N版本窗口)不再出现 404

5. 关键实现点(我们做了什么)

  • Vite 输出使用内容哈希:
    • entry/chunk/asset 文件名:assets/[name]-[hash].js|css|...
    • 不再在文件名中带版本号目录(避免同内容不同路径导致缓存失效)
  • 资源聚合器(脚本):
    • 扫描 dist/versions/ 最近 N 个版本的 assets
    • 统一拷贝(或硬链接/软链接)到 dist/assets
    • 清理 dist/assets 中超出窗口的旧文件
  • 入口与资源分离:
    • dist/current 只放 HTML 与轻量入口文件,允许替换
    • dist/assets 仅追加,不随发布清空
  • 预览/线上服务:
    • /dist/current(HTML 短缓存)
    • /assets/*dist/assets(long cache + immutable)
    • 可选兜底:未命中再从 dist/build/h5/assets 查找(用于本地预览与排查)

6. Service Worker 要点(有用但非必须)

  • SW 对 /assets/* 使用"缓存优先 + 网络兜底"或"网络优先 + 缓存兜底",保证 miss 时回源
  • sw.js、index.html 设短缓存(或 no-cache),SW 能及时更新
  • 若暂不使用 SW,本方案也能零 404;SW 仅作为进一步优化

7. 为什么很多文章不强调"多版本零404"?

  • 默认"旧文件不删":使用 OSS/CDN/对象存储,资源是追加上传,旧对象长期存在
  • 痛点阈值:发版不频繁、用户会刷新、会话短;小概率 404 用"刷新提示"即可
  • 文档聚焦"原则":内容哈希 + immutable + HTML 短缓存,被当作默认前置

当你遇到高频发版、长会话、动态导入较多、不能打断用户的场景时,就必须系统性解决"过渡期 404"。


8. 其他常见做法(简短说法,便于对比)

  • 对象存储/CDN是追加写:
    • 带 hash 的静态资源上传到固定前缀,永不覆盖、不清理
    • 每次发版只上传新增;HTML 短缓存可覆盖
  • 原子部署但"只切入口,不清资源":
    • /releases/<time_hash>/current 指向最新 release;assets 不删
    • 回滚只切回旧 /current
  • Web 服务器层"只换指针,不清资产":
    • Nginx/OpenResty:/ alias 到当前 HTML 目录;/assets alias 到公共资产池
  • 构建/同步策略"追加而非覆盖":
    • rsync/脚本:跳过已存在同名文件,只追加;不执行 rm -rf assets
  • CDN 层"天然长留":
    • 源站是 OSS,CDN 默认不删历史对象;只刷新 HTML

一句话:避免 404 依赖"源站/存储保留旧文件",不是依赖"浏览器缓存"。


9. 我们的流程清单

  • 统一路径:页面永远从 /assets 加载资源
  • 发布顺序:builddeploy:sync(聚合) → 切换 current
  • 保留策略:versions 保留 10 个档案;assets 保留最近 3~5 个版本资源
  • 清理策略:仅清理超出窗口的文件;不清空 assets 目录
  • 观察/回滚:问题时只切换 HTML(current),资源无需变动
    -(可选)SW:/assets 有兜底;HTML/ sw.js 短缓存

10. FAQ(你可能会问)

  • Q:为什么不用把版本号加进路径(如 /v1.0.5/assets/...)?

    • A:这样会让相同内容的 URL 不同,缓存无法复用。内容哈希已能区分新旧;版本应体现在"保留与聚合"的策略,不应体现在资源 URL。
  • Q:把聚合池直接放到 dist/build/h5/assets 可以吗?

    • A:可以,但构建时常会清空该目录,易被覆盖。更稳妥是用 dist/assets 做聚合池;若坚持放 build 目录,务必在 build 之后再做聚合,并避免之后再执行 build。
  • Q:磁盘会不会涨?

    • A:带 hash 的资源相同内容只存一份;保留 3~5 个版本通常增长有限。也可配置自动清理超出窗口的文件。

11. 总结

  • 真因:404 不是"没缓存",而是"源站删了旧文件"
  • 原则:内容哈希 + immutable + HTML 短缓存
  • 方法:/assets 聚合最近 N 个版本资源 + /versions 备份入口
  • 效果:零 404、缓存最优、秒级回滚、可观测、易维护

当发布频繁、会话长、需要极致稳定体验时,这套工程化方案能显著提升质量与口碑。

相关推荐
前端大卫17 分钟前
Vue3 + Element-Plus 自定义虚拟表格滚动实现方案【附源码】
前端
却尘33 分钟前
Next.js 请求最佳实践 - vercel 2026一月发布指南
前端·react.js·next.js
ccnocare34 分钟前
浅浅看一下设计模式
前端
Lee川37 分钟前
🎬 从标签到屏幕:揭秘现代网页构建与适配之道
前端·面试
Ticnix1 小时前
ECharts初始化、销毁、resize 适配组件封装(含完整封装代码)
前端·echarts
纯爱掌门人1 小时前
终焉轮回里,藏着 AI 与人类的答案
前端·人工智能·aigc
twl1 小时前
OpenClaw 深度技术解析
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Grok API 申请及使用
前端
星光不问赶路人1 小时前
vue3使用jsx语法详解
前端·vue.js
天蓝色的鱼鱼1 小时前
shadcn/ui,给你一个真正可控的UI组件库
前端