Skip to main content

BFCache 往返缓存优化方案

针对您列出的 5 个导致 www.bitunix.com 无法使用 BFCache(往返缓存)的原因,我为您提供一份可落地的优化方案

核心结论:您的页面存在3 类核心问题(卸载监听器、主文档缓存头、运行期资源/连接),其中 WebSocket 和 no-store 子资源是最大的优化难点,需要从架构层面调整。


1. 主框架存在 unload 事件监听器(Actionable)

问题unload 事件会破坏 BFCache,因为浏览器为了安全,必须销毁页面才能执行该监听器。 优化方案

  • 立即替换:将所有 window.addEventListener('unload', handler) 替换为 window.addEventListener('pagehide', handler)
  • 兼容处理pagehide 事件同样能捕获页面卸载(关闭、刷新、跳转),且不会阻止 BFCache。
  • 代码示例
    // ❌ 禁止
    window.addEventListener('unload', cleanup);

    // ✅ 推荐
    window.addEventListener('pagehide', (event) => {
    // 如果页面将被存入 BFCache,event.persisted 为 true
    if (!event.persisted) {
    // 仅在不缓存时执行销毁性极强的操作(如关闭 IndexedDB 连接)
    }
    // 轻量级清理(如取消 pending 请求)可以无条件执行
    });

2. & 3. 主文档响应头包含 Cache-Control: no-store(Actionable & Not actionable)

问题:Chrome 判定主文档(HTML)含有 no-store,直接禁止缓存。第 3 条是第 2 条的底层报错复述,解决源头即可。 优化方案

  • 修改服务器(Nginx/Apache/Node)响应头:移除 Cache-Control: no-store, no-cache, must-revalidate
  • 推荐替代方案(既能利用 CDN 加速,又能启用 BFCache):
    Cache-Control: private, max-age=0, must-revalidate

    为什么不用 max-age=3600 加密货币/交易所页面数据实时性极高,使用 must-revalidate 可保证每次刷新都回源校验(304),但允许浏览器保留渲染快照用于 BFCache。


4. JS 网络请求(Fetch/XHR)返回了 Cache-Control: no-store(Not actionable)

问题:页面运行时发起的 API 请求(如行情接口、用户余额)带了 no-store,导致整个页面被“连坐”踢出 BFCache。 优化方案

  • 分级处理(关键):区分“静态资源”和“动态数据”的缓存策略。
    • 静态 JS/CSS/字体:去掉 no-store,改用 Cache-Control: max-age=31536000, immutable(配合文件名 Hash)。
    • 敏感动态数据(余额/交易记录)不能直接去掉 no-store(否则数据会缓存在磁盘),但为了 BFCache,可改为:
      Cache-Control: private, max-age=0, no-cache
      no-cache 允许缓存但每次必须校验,BFCache 允许这种头,而 no-store 绝对禁止)。
  • 代码层优化:如果接口无法改头(第三方依赖),请确保这些请求不在页面 "unload" 或 "visibilitychange" 阶段未完成,但 BFCache 拦截与请求时序无关,必须改响应头

5. 页面使用了 WebSocket(Not actionable)

问题:BFCache 会挂起页面脚本,但 WebSocket 连接无法被序列化挂起,因此只要页面生命周期内存在过 WebSocket,Chrome 默认禁用 BFCache。 优化方案(高频交易/行情页面必看):

  • 主动切断策略:监听 pagehide 事件,在页面进入 BFCache 之前主动关闭 WS 连接。
    let ws = new WebSocket('wss://...');

    window.addEventListener('pagehide', () => {
    if (ws.readyState === WebSocket.OPEN) {
    ws.close(1000, 'Page hidden');
    }
    });
  • 重新连接策略:监听 pageshow 事件,若 event.persistedtrue(从 BFCache 恢复),立即重连 WebSocket
    window.addEventListener('pageshow', (event) => {
    if (event.persisted) {
    // 重新初始化 WebSocket 连接
    reconnectWebSocket();
    }
    });
  • 终极方案:若 WebSocket 用于必须 7x24 小时不间断的心跳维持(如订单推送),且无法接受重连瞬间的数据丢失,则必须放弃 BFCache。此时只能优化页面加载速度(如使用 Service Worker)来弥补返回速度。

针对 www.bitunix.com 的综合落地步骤(优先级排序)

优先级动作预期收益
P0全局搜索 onunloadaddEventListener('unload'),全部替换为 pagehide立刻消除 1 个致命拦截点。
P0Nginx 配置中删除主 HTML 的 no-store,替换为 private, max-age=0, must-revalidate消除 2 个致命拦截点。
P1审计所有 API 接口(尤其是 /api/quote 类),移除响应头中的 no-store,改用 no-cache消除第 4 个拦截点。
P2pagehide 中关闭 WS,在 pageshow 中重连 WS。消除第 5 个拦截点。若业务无法接受重连,则接受 BFCache 不可用。

完成上述修改后,在 Chrome DevTools 的 Application > Back/forward Cache 面板点击 Test,即可验证是否全部通过。