前端部署后如何判断“页面是不是最新”?一套可落地的版本检测方案(适配 Vite/Vue/React/任意 SPA)

前端部署后如何判断"页面是不是最新"?一套可落地的版本检测方案(适配 Vite/Vue/React/任意 SPA)

线上发版最尴尬的事:你以为发成功了,结果用户/测试打开的还是旧页面(缓存命中)

这篇文章给你一套"能自测、能提示更新、能强制刷新、能证明发版成功"的通用方案:
构建时生成版本标识 → 线上暴露 version 文件 → 客户端对比 → 发现更新后提示/强更。


1. 为什么部署后页面可能不是最新?

常见原因就三类:

  1. 静态资源强缓存index.htmljs/css 被 CDN/浏览器缓存,用户拿到旧资源
  2. Service Worker:PWA/离线缓存没更新,导致一直返回旧资源
  3. 灰度/多节点:CDN/多机房更新不一致,用户命中旧节点

所以"判断是否为最新",本质是:
让客户端拿到一个"不会被缓存的版本标识",再和本地版本对比。


2. 总体思路:版本标识(buildId/buildTime)+ 对比 + 处理

2.1 版本标识怎么选?

推荐两个选择:

  • buildTime :时间戳(如 20251220141600

    • 优点:简单直观,肉眼能看懂
    • 缺点:并不能天然保证"唯一"(同一秒多次 build 需要更细粒度)
  • buildId :Git Commit + 时间(如 a1b2c3d-20251221-1416

    • 优点:唯一、可追溯,排查问题一眼定位版本
    • 缺点:需要 CI 传入 Git 信息

✅ 最佳实践:buildId = commitSha + buildTime

既唯一,又可定位。


3. 方案一(轻量):构建时注入变量,在控制台/页面展示

适用:本地自测、提测快速确认、排查"我现在到底打开的哪个版本"

不适用:线上自动提示更新

3.1 Vite 注入(推荐)

js 复制代码
// vite.config.js
import { defineConfig } from 'vite'

const BUILD_TIME = new Date().toISOString()

export default defineConfig({
  define: {
    __BUILD_TIME__: JSON.stringify(BUILD_TIME),
  },
})
// main.js
console.log('[BUILD_TIME]', __BUILD_TIME__)

3.2 Vue CLI 注入

js 复制代码
// vue.config.js
const webpack = require('webpack')
const BUILD_TIME = new Date().toISOString()

module.exports = {
  configureWebpack: {
    plugins: [
      new webpack.DefinePlugin({
        __BUILD_TIME__: JSON.stringify(BUILD_TIME),
      }),
    ],
  },
}

✅ 建议加一个"页面可视化入口":

例如在 About/设置页显示版本号,或在全局错误页展示 buildId,线上排查巨省时间。


4. 方案二(生产可用):生成 version.json(或 version.txt)+ 轮询对比

这是最通用、最稳定的方案:
构建结束生成 dist/version.json,部署后客户端轮询拉取,发现变化就提示刷新/强更。

4.1 生成 version.json(postbuild)

dist/version.json 示例:

json 复制代码
{
  "buildTime": "2025-12-21T14:16:00.000Z",
  "buildId": "a1b2c3d-20251221-1416"
}
package.json
json 复制代码
{
  "scripts": {
    "build": "vite build",
    "postbuild": "node scripts/generate-version-dist.mjs"
  }
}
scripts/generate-version-dist.mjs
js 复制代码
import { writeFile } from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const root = path.resolve(__dirname, '..')

// 你可以在 CI 里注入 COMMIT_SHA
const COMMIT_SHA = process.env.COMMIT_SHA || 'dev'

async function main() {
  const buildTime = new Date().toISOString()
  const buildId = `${COMMIT_SHA}-${buildTime.replace(/[-:TZ.]/g, '').slice(0, 12)}`

  const outPath = path.join(root, 'dist', 'version.json')
  await writeFile(outPath, JSON.stringify({ buildTime, buildId }, null, 2) + '\n', 'utf-8')
}

main().catch((e) => {
  console.error('[generate-version] failed', e)
  process.exit(1)
})

✅ 注意:version.json 不需要提交 Git!

更推荐在 CI 构建产物里生成,随产物一起发布,避免"忘了 commit 版本号文件"。


5. 客户端实现:轮询拉取 + 对比 + 更新策略

你原文的 versionPoller 思路很好,我这里给一个更偏"生产级"的改造点:

  • 禁止缓存cache: 'no-store' + ?t=timestamp
  • 指数退避:失败时逐步延长间隔,防止网络差导致打爆服务
  • 页面不可见暂停:切后台就停,回前台再继续
  • 更新策略可选
    • 软提示:弹窗"有新版本,点击刷新"
    • 半强制:重要业务更新后强制刷新
    • 强制:直接 location.reload(true)(现代浏览器 true 已不严格区分,但语义保留)

5.1 推荐的版本对比逻辑

  • 本地存:localStorage.__BUILD_ID__
  • 远端取:version.json.buildId
  • 不一致:触发更新

5.2 关键实现(精简版)

js 复制代码
export function createVersionPoller({
  versionUrl = '/version.json',
  storageKey = '__VERSION_CHECK__BUILD_ID__',
  intervalMs = 15000,
  maxDelayMs = 60000,
  onUpdate,
  onResult,
  onError,
} = {}) {
  let timer = null
  let stopped = true
  let delayMs = intervalMs

  const readLocal = () => localStorage.getItem(storageKey) || ''
  const writeLocal = (v) => v && localStorage.setItem(storageKey, String(v))

  const isVisible = () => document.visibilityState !== 'hidden'
  const clear = () => timer && clearTimeout(timer)

  async function fetchRemote() {
    const sep = versionUrl.includes('?') ? '&' : '?'
    const url = `${versionUrl}${sep}t=${Date.now()}`
    const res = await fetch(url, { cache: 'no-store', headers: { 'Cache-Control': 'no-cache' } })
    if (!res.ok) throw new Error(`fetch version failed: ${res.status}`)
    return await res.json()
  }

  async function tick() {
    if (stopped || !isVisible()) return

    const localBuildId = readLocal()

    try {
      const data = await fetchRemote()
      const remoteBuildId = String(data?.buildId || '')
      const remoteBuildTime = String(data?.buildTime || '')

      delayMs = intervalMs
      onResult?.({ localBuildId, remoteBuildId, remoteBuildTime, data })

      if (remoteBuildId && remoteBuildId !== localBuildId) {
        writeLocal(remoteBuildId) // 防重复提示
        onUpdate?.({ localBuildId, remoteBuildId, remoteBuildTime, data })
        return
      }
    } catch (e) {
      delayMs = Math.min(maxDelayMs, Math.max(500, delayMs * 2))
      onError?.(e)
    }

    timer = setTimeout(tick, delayMs)
  }

  function onVisibilityChange() {
    if (stopped) return
    clear()
    if (isVisible()) timer = setTimeout(tick, 0)
  }

  function start() {
    if (!stopped) return
    stopped = false
    delayMs = intervalMs
    document.addEventListener('visibilitychange', onVisibilityChange)
    window.addEventListener('beforeunload', stop)
    if (isVisible()) timer = setTimeout(tick, 0)
  }

  function stop() {
    stopped = true
    clear()
    document.removeEventListener('visibilitychange', onVisibilityChange)
    window.removeEventListener('beforeunload', stop)
  }

  return { start, stop }
}

5.3 更新后的处理方式(推荐弹窗 + 刷新)

js 复制代码
createVersionPoller({
  onUpdate: ({ remoteBuildId }) => {
    // 你可以用组件弹窗
    if (confirm(`检测到新版本:${remoteBuildId}\n是否立即刷新?`)) {
      window.location.reload()
    }
  },
}).start()

6. 关键注意点(决定你方案"好不好用")

6.1 version.json 必须尽量"不缓存"

你已经做了 ?t=,还建议在 CDN/网关层对 version.json 单独配置:

  • Cache-Control: no-store 或极短缓存
  • 或者把它放到"不会被 CDN 强缓存"的路径规则里

6.2 不要轮询太频繁

  • 15s~60s 是常见区间
  • 失败退避是必须的(不然弱网用户会把你服务打爆)

6.3 配合"文件名 hash"才是真正意义的缓存正确姿势

生产里真正的最佳实践是:

  • js/css 走 content-hash(长期缓存)
  • index.html 短缓存/不缓存
  • version.json 不缓存
    这样既能充分利用缓存,又能快速感知新版本。

6.4 如果你用了 Service Worker,一定要处理 SW 更新

否则你会发现:version.json 都更新了,但 SW 还是给你吐旧资源。

SW 场景要额外做:

  • skipWaiting
  • clientsClaim
  • 发现新 SW 后提示用户刷新(或自动刷新)

7. 总结

  • 构建时生成版本标识(buildTime/buildId)
  • 部署产物暴露 version.json
  • 客户端轮询对比 + 发现更新后提示刷新
  • 配合缓存策略(hash 静态资源 + index/version 不缓存)
  • 必要时考虑 Service Worker 更新链路

这套方案的核心价值是:
提测/自测能证明"我现在访问的是最新代码",线上能优雅提示用户刷新,避免缓存导致的"伪发版成功"。

相关推荐
GISer_Jing1 小时前
AI前端(From豆包)
前端·aigc·ai编程
测试修炼手册1 小时前
[测试技术] 深入理解 JSON Web Token (JWT)
前端·json
AI老李1 小时前
2026 年 Web 前端开发的 8 个趋势!
前端
里欧跑得慢1 小时前
15. Web可访问性最佳实践:让每个用户都能平等访问
前端·css·flutter·web
钛态1 小时前
前端趋势:别被时代抛弃
前端·vue·react·web
小码哥_常1 小时前
Room 3.0:移动端持久化的“重生”变革
前端
Beginner x_u2 小时前
链表专题:JS 实现原理与高频算法题总结
javascript·算法·链表
Front思2 小时前
前端的.hbs
前端
我叫汪枫2 小时前
在后台管理系统中,如何递归和选择保留的思路来过滤菜单
开发语言·javascript·node.js·ecmascript