解决浏览器缓存导致页面非最新的小技巧

解决浏览器缓存导致页面非最新的小技巧

为了保证页面访问性能最佳,我们通常在服务端会设置缓存策略,比如说带有 hash 类型的文件会设置过期时间为永久,
非 hash 文件比如 html 等其他文件设置了通用的缓存策略,即:根据 etag 或者 last-modified 来判断文件是否更改,
然后返回 304 代码告知浏览器不用下载,从而保证页面最新。这些策略在页面加载性能和版本维持最新之间保持了平衡。

为什么普通的策略会出现页面非最新?

通常使用上述策略能够保证页面最新。但是有时候会出现两种情况导致页面非最新:浏览器原因和服务端原因。

  1. 使用 etag 或者 last-modified 存在的问题
    etag 是提供文件指纹的标识,这个标识的实现可以通过多种方式,方式的实现关系着文件是否最新。通常情况下 etag 出现无法刷新的情况比较少,
    出现问题多是因服务器文件系统或者静态文件服务出现了问题。另一种方式是使用 last-modified(部分etag 也可能使用了时间戳)。last-modified
    的精度是精确到秒,对于服务器文件来说这远远不够,1 秒内文件变动可能超过一次,或者出现文件变动但时间戳没有变化的情况,
    导致不论客户端怎么刷新都是返回 304 代码,页面无法更新到最新

  2. 浏览器自身缓存
    如果是遵守 http 规范,保证每次页面加载时都使用服务端内容,那么就不存在缓存问题。但实际上浏览器为了提高性能,
    总会进行缓存。比如说 safari 下页面会整个被缓存起来,chrome 在输入已有网址时会优化从 dist cache 中获取文件,
    而不会去请求服务器。

怎么解决?

对于服务端导致的缓存问题,需要排查出现 304 的原因,针对性的解决,这里就不再介绍了。

对于浏览器的问题,有两种:页面全部缓存和只是缓存文件。

  1. 全部缓存页面
    这种情况通常出现在 safari 后退或者内存不够重新加载时,特点是页面所有信息都被缓存起来,这时候想要重新请求页面文件是没有任何办法的,
    只能上显示时判断是否需要进行页面的刷新。由于页面缓存后不执行 onload 事件,需要在 onpageshow 事件中判断是否需要重新加载页面。

  2. 文件被缓存
    前端页面缓存的判断是包含了文件是否为最新的判断。

基础实现

最简单的做法是提供一个 API,保存版本信息,每次加载时请求最新版本信息,
如果不是最新则提示。基本逻辑如下:

fetch('/page/version').then((res) => res.json())
  .then((res) => {
    if (res && res.version && res.version !== 当前的版本) {
      提示或者刷新
    }
  })

遇到的问题是:当前页面的版本如何保存和怎么变动工作量最小?

版本保存我们可以以文件的方式进行保存,通过 import 后,固定在代码中。这样就可以实现基本的工作。

简化实现

基础实现存在的问题是:版本需要提供独立的 API,对于前端来说配合过程太麻烦。那么我们就考虑下版本信息放在文件中。
放在文件中首先要解决的一个问题是:文件会被缓存,而且比较严重。解决这个问题就在 url 中添加时间戳,保证每次 URL 不同,

最终做法如下:

  • version 文件存放在 static 或者 public 文件下,这样在打包后会将该文件复制到文件下。
  • 当前版本保存:通过js import 进文件,导入变量
  • 获取服务端版本:请求静态文件(添加时间戳保证不被缓存
  • 对比:服务端版本存在且不等于当前版本,提供通知,让用户手动刷新,或者直接使用 window.location.reload(true)

代码如下(使用 antd 中的 notication 进行通知,其他类型的通知也类似):

import { notification } from 'antd'
import page from '../../public/pageVersion.json'

fetch(`/pageVersion.json?_=${Date.now()}`).then(res => res.json())
  .then((res) => {
    if (res.version && page.version !== res.version) {
      notification.open({
        message: '页面过期',
        description: `当前页面已经过期,最近更新时间为${res.updateTime},请手动刷新浏览器页面以便获取更好体验`,
        duration: null
      })
    }
  })

export default {}

当然,你可以做一些交互或者其他更复杂的操作,完全看个人需求了。

posted @ 2018-09-06 11:49 无梦灬 阅读(...) 评论(...) 编辑 收藏