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())
}
}以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)
)
}
}# 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() | 用户活跃时多一次检测 |
布莱克的个人微信小程序

© 2025 - 2026 Black Blog. All rights reserved. | 豫ICP备2025153558号-1
© 2025 Black Blog. All rights reserved.
豫ICP备2025153558号-1
