前端部署后的版本检测与自动更新
问题现象: SPA 应用部署后,已打开页面的用户不会自动加载新版本 部署前(用户当前运行版本 v1) ├── index.html # 引用 _nuxt/abc123.js └── _nuxt/ └── abc123.js 部署后(服务器版本 v2) ├── index.html # 引用 _nuxt/def789.js └── _nuxt/ ├── abc123.js # 已删除 └── def789.js 技术方案对比 方案 实时性 实现成本 服务器开销 适用场景 WebSocket 推送 毫秒级 高 需维护长连接 实时协作、IM SSE 毫秒级 中 需服务端支持 通知、监控面板 轮询 0-5min 低 极低 内容型网站、后台管理 我的方案选择是轮询,成本较低,且五分钟的延迟对用户体验影响较小 ┌─────────────────────────────────────────────────────────────────┐ │ 构建阶段 │ │ ┌─────────────┐ ┌─────────────────────────────────────┐ │ │ │ 生成版本号 │ → │ 写入 build-version.json + 注入到 │ │ │ │ (Git SHA) │ │ 运行时配置 (appBuildVersion) │ │ │ └─────────────┘ └─────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ 部署阶段 │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ Nginx 配置: 静态资源长缓存 + JSON 不缓存 + 备份清理 │ │ │ └─────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────────────────┐ │ 运行阶段 │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ 立即检测一次 │ → │ 每 5 分钟轮询 │ → │ 版本不一致 │ │ │ │ │ │ │ │ 弹窗通知刷新 │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ ↑ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ visibilitychange 控制轮询启停 + 切回时立即检测 │ │ │ └─────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ 模块职责 模块 职责 版本号生成 每次构建生成唯一标识 构建注入 写入 build-version.json ,注入运行时配置 Nginx 路由 静态资源服务 + 缓存策略控制 前端检测插件 轮询、对比版本、通知、生命周期控制 版本号生成策略 //优先git命令 获取当前代码仓库最新提交的短哈希值 //时间戳兜底 function getAppBuildVersion() { try { return execSync('git rev-parse --short HEAD', { encoding: 'utf8' }).trim() } catch { return String(Date.now()) } } 构建时写入version.json 以Nuxt项目为例,不同项目写法不同 build-version.json 必须放在 public 目录下 hooks: { compiled(nitro) { const publicDir = nitro.options.output.publicDir mkdirSync(publicDir, { recursive: true }) writeFileSync( join(publicDir, 'build-version.json'), JSON.stringify({ version: appBuildVersion }, null, 2) ) } } Nginx配置: # JSON 文件:禁止缓存(核心配置) location ~* \.json$ { root /var/nuxt-blog/.output/public; try_files $uri =404; access_log off; add_header Cache-Control "no-cache, no-store, must-revalidate"; } 核心检测逻辑: const check = async () => { if (!localVersion) return // ← 本地没有版本号,无法对比,直接退出 try { // 1. 请求服务器版本文件 const res = await fetch(`${VERSION_URL}?t=${Date.now()}`, { cache: 'no-store' // ← 强制不走浏览器缓存 }) if (!res.ok) return // ← 请求失败(404/500等),静默退出 const data = await res.json() // ← 解析 JSON // 2. 对比版本号 if (data?.version && data.version !== localVersion) { notify() // ← 不一致 → 弹窗! } } catch { // 捕获异常,静默处理,不打断用户阅读 } } 正常用户访问流程: 时间点 ① 用户首次打开页面(版本 v1) 服务器上有 build-version.json → { version: "v1" } 构建时注入到页面的 localVersion = "v1" 时间点 ② 用户发起 fetch(/build-version.json) 服务器返回 { version: "v1" } 对比:localVersion ("v1") === json.version ("v1") ✅ 一致 → 不弹窗 时间点 ③ 你部署了新版本 v2 服务器上的 build-version.json 变成了 { version: "v2" } ⚠️ 但用户浏览器里还跑着旧页面(没刷新) 时间点 ④ 用户页面里的 localVersion 仍然是构建时注入的旧值 "v1" (因为页面本身没刷新,JS 上下文没重建) 时间点 ⑤ 用户发起 fetch(/build-version.json) 服务器返回 { version: "v2" } 对比:localVersion ("v1") !== json.version ("v2") 🔔 不一致 → 弹窗! 核心流程编排: const start = () => { // 1. 立即执行一次检测 throttledCheck() // 2. 启动定时轮询 startPolling() // 3. 监听页面可见性变化 document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') { startPolling() // ← 用户回来了,恢复轮询 throttledCheck() // ← 立即检测一次 return } stopPolling() // ← 用户切到后台,停止轮询 }) // 4. 用户聚焦页面时触发检测 window.addEventListener('focus', throttledCheck) } 行为 触发 作用 切到后台 stopPolling() 节省 CPU/电量 切回前台 startPolling() + throttledCheck() 恢复检测,立即查一次 点击页面 throttledCheck() 用户活跃时多一次检测
布莱克2026-08-18 22:33部署已编辑