Appearance
发版 404 报错 菜单无反应
本节目标:理解“发版 404 报错 菜单无反应”的核心思路,并能把它用于实际开发或面试表达。 你遇到的是典型的 “旧入口(index.html/路由表) + 新资源(按需 chunk) 不一致” 问题:用户停留在旧版本页面里,点击菜单时才去请求某个异步 chunk;但你们发版后把旧 chunk 从服务器清掉了,于是动态 import() 直接 404(表现为报错、点击无反应、白屏一块等)。 要把这个问题“治本”,核心就一句话:发版时不要让旧版本引用到的静态资源消失。推荐按优先级做成“部署策略 + 前端兜底”两层。
1) 最佳治本:静态资源“不可变”并保留一段时间(强烈推荐) 前端构建产物(Vite/webpack)通常已经是 content-hash 文件名(例如 chunk.8fd3a1.js),天然适合“只增不删”。 你们现在出问题的根因一般是部署在覆盖目录时用了类似 rsync --delete / 清空 dist 再上传,导致旧 hash 文件被删。 改法(最简单且效果最好):
- 发版时:只上传新增文件 + 更新 index.html,但不要删除旧的 assets/*.js。
- 清理策略:保留最近 N 次构建的资源文件,或保留最近 X 天;再定时清理“完全不再被任何入口引用”的旧文件。
这样,哪怕用户的 tab 开着一整天,点菜单时仍能拉到它旧版本所需的 chunk,不会 404。
2) 更工程化:版本化目录 + 原子切换(适合严谨团队) 把每次构建放到独立目录,例如:
- /static/releases/20251217_1030/index.html
- /static/releases/20251217_1030/assets/...
- /static/releases/20251218_0915/...
入口通过 “原子切换” 指向最新版本(改软链或改反代指向),并保留旧 release 目录一段时间。 优点:回滚/灰度非常舒服;天然不会“旧入口指向新目录导致找不到旧 chunk”。 缺点:发布系统/服务器配置要更规范一点。
3) 必做配套:缓存策略正确,减少“入口长期不更新” 即使你保留旧 chunk,合理的缓存也能显著降低命中旧入口的概率:
- index.html:不要强缓存(让浏览器每次都能协商拿最新入口)
- 带 hash 的 js/css:强缓存一年 + immutable(性能更好,也不会更新冲突)
Nginx 示例(只给关键片段):
入口不要强缓存,尽量让用户尽快拿到新版本入口location = /index.html { add_header Cache-Control "no-cache";}
hash 静态资源可以长期缓存location /assets/ { add_header Cache-Control "public, max-age=31536000, immutable";}
4) 前端兜底:捕获“chunk 加载失败”并提示刷新(提升体验) 就算你做了资源保留,仍可能遇到网络抖动/CDN 回源问题,所以建议加一个兜底:路由异步组件加载失败时,引导用户刷新拿到最新入口。 Vue Router 4(只给需要加的片段): // 菜单对应的异步 chunk 拉取失败时,自动刷新一次以获取最新入口文件,避免用户“点了没反应”router.onError((err) => { const msg = (err as Error).message || '' if (/Failed to fetch dynamically imported module|Loading chunk .* failed|ChunkLoadError/i.test(msg)) { const tried = sessionStorage.getItem('chunk_reload') if (tried !== '1') { sessionStorage.setItem('chunk_reload', '1') location.reload() return } sessionStorage.removeItem('chunk_reload') } console.error(err)}) 这个兜底只能“缓解/自救”,治本仍是第 1/2 条,否则刷新也只是更早暴露资源缺失。
我给你的“最佳落地组合”
- 部署层:发版不要删除旧 assets(或做版本目录保留 2~5 个 release / 7~30 天)。
- 缓存层:index.html no-cache;hash 资源 immutable 长缓存。
- 前端层:router.onError 捕获 chunk load error → 弹提示/自动刷新一次。
如果你告诉我你们是 Vite 还是 webpack、以及部署是 直接覆盖 dist / rsync / Docker 镜像 / CDN 哪一种,我可以把“发布脚本/目录策略”给你一个更贴合你们现状、改动最小的方案(仍然只给需要改的片段)。
这个报错本质上就是:用户当前打开的是旧版本入口文件里引用的 chunk,但你们发版时把旧的 /assets/index-D6HALCiG.js 删除/覆盖掉了,所以动态加载直接 404,路由导航 Promise 被 reject,最终表现为“点击菜单无反应/停在原页面”。 从你栈里也能对应上:
- onMenuClick -> router.push -> finalizeNavigation
- 路由组件触发 preload / 动态 import
- 资源 404(net::ERR_ABORTED 404)=> 导航被中断
下面给你一个最推荐的组合:部署治本 + 前端兜底(两者都做,体验会非常稳)。
1**)治本:发版时不要删除旧的** hash 静态资源 只要你们构建产物是这种带 hash 的文件名(你这里就是 index-D6HALCiG.js),正确策略就是**“旧文件留存一段时间”**:
- 发版:上传新文件 + 更新 index.html
- 不要清空 dist / 不要 rsync --delete 把旧 /assets/*.js 删掉
- 定时清理:保留最近 N 次构建或最近 X 天(比如 7~30 天)
这是唯一能从根上消灭 404 chunk 的方式。前端再怎么兜底都只是“自救”。
2**)缓存建议:入口不强缓存,**hash 资源强缓存 如果入口 index.html 被强缓存,用户更容易长期停留在旧入口,触发你这个问题。
- index.html:Cache-Control: no-cache(或至少协商缓存)
- /assets/*.(js|css):max-age=31536000, immutable
3**)前端兜底:捕获****“chunk** 加载失败**”并自动刷新一次 这能解决你说的“点击菜单无反应”,即便后端偶尔清理了旧文件,也能让用户自动拿到新入口恢复可用。 3.1 在 router 初始化处加 router.onError // 路由懒加载的 chunk 404/加载失败时,自动刷新一次以获取最新版本入口router.onError((err: unknown) => { const msg = err instanceof Error ? err.message : (typeof err === 'string' ? err : '') if (/Failed to fetch dynamically imported module|Loading chunk . failed|ChunkLoadError/i.test(msg)) { const key = 'chunk_reload' if (sessionStorage.getItem(key) !== '1') { sessionStorage.setItem(key, '1') location.reload() return } sessionStorage.removeItem(key) } console.error(err)}) 3.2 再补一个 unhandledrejection*(覆盖非路由触发的动态** import**)** // 动态 import 的 Promise 未被捕获时(例如某些组件内懒加载),同样做一次刷新兜底window.addEventListener('unhandledrejection', (e: PromiseRejectionEvent) => { const reason = e.reason const msg = reason instanceof Error ? reason.message : (typeof reason === 'string' ? reason : '') if (/Failed to fetch dynamically imported module|Loading chunk .* failed|ChunkLoadError/i.test(msg)) { const key = 'chunk_reload' if (sessionStorage.getItem(key) !== '1') { sessionStorage.setItem(key, '1') location.reload() } }})
你现在这个环境最可能的真实原因(按概率)
- 部署脚本清空**/**覆盖 dist(最常见:先删再传 / rsync 带 --delete)
- 入口 index.html 被缓存得太久(用户拿不到新入口)
- CDN/反代回源不一致(新入口已经生效,但某些边缘节点缺旧 chunk)
如果你把你们的发布方式(比如:scp 覆盖、rsync、Docker 镜像、还是 OSS+CDN)和Nginx/****网关是否有缓存贴一下,我可以直接告诉你“发布脚本该改哪两行、Nginx 该加哪几条 location”,保证这个 404 chunk 在你们的 staging 上彻底消失。
