前端页面更新检测实战:一次关于「用户不刷新」的需求拉扯战

这是我在真实项目里为「用户不刷新看不到新页面」做的一次完整治理,从方案推演、和产品拉扯,到最终落地 无感知版本更新提示 的全过程。所有代码以 Vite + Vue3 为例。

1. 🎬 开场:深夜工位的"灵魂三问"

产品(10:02 PM)

新的页面不是刚上线吗,怎么用户还是看到老版本?

我(10:03 PM)

让用户刷新一下页面,或者清下缓存就好了。

产品(10:04 PM)

我要告诉用户"请刷新",这体验太差了。能不能自动刷新?

我(OS)

......(明明就是 SPA + Nginx 静态资源,这事儿不复杂,但真想做好,也不简单。)


2. 🧭 背景与约束

  • 架构:Vue3 + Vite 打包,Nginx 托管静态资源
  • 缓存index.html协商缓存 (Etag/Last-Modified),*.js/*.css强缓存Cache-Control + 文件名带 hash)
  • 真实场景 :用户长时间停留 在页面不动;前端上线后,不清空缓存刷新就永远看不到新版本
  • 风险
    1. 后端接口变更,老页面还在调用旧协议,线上报错
    2. 修 bug 后,用户还在用旧代码,无法获得修复
    3. 提示文案错误,必须尽快全量生效

于是,我们需要一个前端可控、无后端强依赖、能温和推动用户刷新的方案。


3. 🕰 时间线:从"拍脑袋"到"可灰度的工程方案"

Day 1:先跟产品吵一架(然后一起找共识)

  • 产品 :我就要"用户完全无感知,像原生 App 一样自动更新"。

  • :Web 不是 App。强制刷新会打断用户交互,会丢失表单输入数据。

  • 折中方案

    • 若当前无风险操作:弹一个"新版本可用"提示 → 用户确认后刷新
    • 若风险更新(协议变更、紧急修复) :在下一次用户点击可控元素时触发刷新(给用户一个可预期的时机)
    • 对超长驻留页 :默默在后台定时检测版本 ,可选静默预加载资源

4. ⚔️ 解决方案

方案一:轮询检测 index.html 的 Etag

最简单粗暴的办法:

  • 定时向服务器请求一次首页 HTML。
  • 如果发现 Etag 变化,提示用户刷新。

App.vue中添加如下代码

js 复制代码
<script setup>
import { ref, onMounted } from 'vue';
import { Modal } from 'ant-design-vue';

const oldHtmlEtag = ref();
const timer = ref();

const getHtmlEtag = async () => {
  const { protocol, host } = window.location;
  const res = await fetch(`${protocol}//${host}`, {
    headers: { "Cache-Control": "no-cache" },
  });
  return res.headers.get("Etag");
};

onMounted(async () => {
  oldHtmlEtag.value = await getHtmlEtag();
  clearInterval(timer.value);
  timer.value = setInterval(async () => {
    const newHtmlEtag = await getHtmlEtag();
    if (newHtmlEtag !== oldHtmlEtag.value) {
      Modal.confirm({
        title: "检测到新版本,是否更新?",
        okText: "更新",
        cancelText: "取消",
        onOk: () => window.location.reload(),
      });
    }
  }, 30000); // 每 30 秒检测一次
});
</script>

产品(犹豫) :30 秒一次会不会太频繁?
:不然用户等半天都没感知到更新。


方案二:版本文件 versionData.json

为了减少请求压力,我们想到生成一个 versionData.json 文件,记录版本号。

自定义 Vite 插件plugins/vitePluginCheckVersion.ts

js 复制代码
import path from "path";
import fs from "fs";

export function checkVersion() {
  return {
    name: "vite-plugin-check-version",
    buildStart() {
      const now = new Date().getTime();
      const version = { version: now };
      const versionPath = path.join(__dirname, "../public/versionData.json");
      fs.writeFileSync(versionPath, JSON.stringify(version), "utf8");
    },
  };
}

并在 vite.config.ts 中引入:

js 复制代码
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { checkVersion } from "./plugins/vitePluginCheckVersion";

export default defineConfig({
  plugins: [
    vue(),
    checkVersion(),
  ]
});

App.vue中定时请求该文件,发现版本号更新则提示刷新页面

js 复制代码
const timer = ref(null)
const checkUpdate = async () => {
  let res = await fetch('/versionData.json', {
    headers: {
      'Cache-Control': 'no-cache',
    },
  }).then((r) => r.json())
  if (!localStorage.getItem('demo_version')) {
    localStorage.setItem('demo_version', res.version)
  } else {
    if (res.version !== localStorage.getItem('demo_version')) {
      localStorage.setItem('demo_version', res.version)
      Modal.confirm({
        title: '检测到新版本,是否更新?',
        content: '新版本内容:' + res.content,
        okText: '更新',
        cancelText: '取消',
        onOk: () => {
          window.location.reload()
        },
      })
    }
  }
}

onMounted(()=>{
  clearInterval(timer.value)
  timer.value = setInterval(async () => {
   checkUpdate()
  }, 30000)
})

方案三:现成库 plugin-web-update-notification

懒人方案,直接用第三方库:

js 复制代码
// vite.config.ts
import { defineConfig } from 'vite';
import vue from '@vitejs/plugin-vue';
import { webUpdateNotice } from '@plugin-web-update-notification/vite';

export default defineConfig({
  plugins: [
    vue(),
    webUpdateNotice({
      logVersion: true,  // 控制台打印版本
      checkInterval: 30 * 1000 // 自定义检查间隔
    }),
  ]
})
  • 优点:方便,省时。
  • 缺点:但定制化不足,UI 可能不符合项目风格。

5. 🏁 最终选择:方案二 + 提示弹窗

经过与产品多轮拉扯,决定:

  • 采用 versionData.json ,性能好,可控性强。
  • 提示弹窗由我们自定义,用户可选择立即刷新或稍后。

上线当天,产品亲自测试:

  • 打开旧页面,静静等待。
  • 弹窗出现------ "检测到新版本,是否更新?"
  • 点了"更新",页面瞬间重载,加载了最新的内容。

产品(满意地点点头) :说道小王,可以加你个wx吗?

看似一个简单的"刷新页面",背后却藏着缓存策略、用户体验、性能优化的多重博弈。

相关推荐
h***346330 分钟前
SpringBoot3.3.0集成Knife4j4.5.0实战
android·前端·后端
Yanni4Night30 分钟前
数据可视化神器Heat.js:让你的数据热起来
前端·javascript
前端一课34 分钟前
【vue高频面试题】第9题:Vue3 的响应式原理是什么?和 Vue2 的响应式有什么区别?为什么 Vue3 改用了 Proxy?
前端·面试
Demon--hx34 分钟前
[C++]迭代器失效问题
前端·c++
GISer_Jing35 分钟前
前端架构学习
前端·学习·架构
前端一课36 分钟前
【vue高频面试题】第4题:Vue 3 中的 setup() 是什么?它的执行时机是什么?能做什么?
前端·面试
前端一课36 分钟前
【vue高频面试题】第5题:Vue3 的父子组件通信方式有哪些?分别适用于什么场景?
前端·面试
Funny Valentine-js37 分钟前
web实验后端php测试文本
前端·javascript·php·html5·cookie·telnet·session
前端一课40 分钟前
【vue高频面试题】第6题:Vue3 中 Composition API 和 Options API 有什么区别?为什么 Composition API 更推荐
前端·面试