配了三年返回键优化,今天才发现你的页面根本没资格被缓存——NotRestoredReasons API 把这件事彻底扒开了

页面返回快的时候,你以为那是用户手机的性能好,或者网络信号强——实际上大多数时候是浏览器自己在内存里捞出来的,叫 bfcache

Chrome 的数据显示:桌面端每 10 次导航有 1 次是点返回/前进,移动端每 5 次就有 1 次。bfcache 命中时页面几乎是 0ms 出来,用户感知极好。但问题是:你的页面到底能不能被 bfcache 命中,你自己其实根本不知道。

今天这件事有了一个正式的诊断工具。

bfcache 命中率到底是多少

bfcache(back/forward cache)是浏览器的一种内存优化机制:用户离开页面时,浏览器不立即销毁页面,而是冻结 JavaScript 执行并把整个页面快照存在内存里。用户点返回时,浏览器直接恢复这个快照,不需要重新请求网络,速度接近 0ms。

Chrome 自己的数据是:桌面 10%、移动 20% 的导航都是返回/前进。但这是用户行为层面的分布,不是你的网站命中率。你的页面可能每 10 次返回里有 8 次都因为某些代码写法被 bfcache 拒绝了,而你完全不知道。

bfcache 命中率和两个因素有关:

  1. 用户行为(关闭标签、重启浏览器后返回,不会走 bfcache)
  2. 页面本身的代码写法(这个是你能控制的)

问题在于第二种情况,大多数团队从来不测。

诊断工具:NotRestoredReasons API

Chrome 125(2025 年)给 PerformanceNavigationTiming 加了一个新属性 notRestoredReasons,专门用来报告「你的页面为什么不能走 bfcache」。

Chrome 128(2026 年)正式稳定,移动端也支持了。Safari 和 Firefox 暂未实现,这是目前的主要局限——但 Chrome 流量足够大,测量价值已经很高。

核心用法,3 行代码:

// 获取当前导航的 bfcache 诊断数据
const navEntry = performance.getEntriesByType("navigation")[0];
console.log(navEntry.notRestoredReasons);

返回的结构长这样——

{
  children: [...],           // iframe 子框架的原因
  id: null,
  name: null,
  reasons: [                 // 阻止原因列表
    { reason: "unload-listener" },
    { reason: "masked" }
  ],
  src: null,
  url: "https://example.com/product/123"
}

reasons 数组里会告诉你具体是什么东西阻止了 bfcache。常见的原因值包括:

  • unload-listener:页面注册了 beforeunloadunload 事件监听器,这是最常见的 bfcache 杀手。Chrome、Firefox 遇到这个直接拒绝缓存。
  • active-text-asset:页面持有正在使用的声音/视频 AudioContext。
  • back-forward-cache-disabled:通过页面策略或实验性配置手动禁用了 bfcache。
  • masked:子框架里有阻止原因,但跨域所以具体原因被隐藏了。

测量 bfcache 命中率的具体方案

方案一:pageshow 事件(最通用)

window.addEventListener("pageshow", (event) => {
  if (event.persisted) {
    // 页面是从 bfcache 恢复的
    console.log("bfcache restore");
  }
});

结合导航类型判断:

const navType = performance.getEntriesByType("navigation")[0].type;
window.addEventListener("pageshow", (event) => {
  if (event.persisted && navType === "back_forward") {
    // 这是真正的 bfcache 命中
  }
});

方案二:GA4 里的实际统计公式

在 Google Analytics 4 里,通过区分导航类型来算命中率:

// 初始加载时记录导航类型
gtag("event", "page_view", {
  navigation_type: performance.getEntriesByType("navigation")[0].type
});

// 每次 pageshow 时补充记录
window.addEventListener("pageshow", (event) => {
  if (event.persisted) {
    gtag("event", "page_view", {
      navigation_type: "back_forward_cache"
    });
  }
});

bfcache 命中率 = back_forward_cache 事件数 / back_forward 导航总数

这个指标能直接反映你的页面有多少返回导航是真正命中了 bfcache 的。如果比例偏低,说明有代码在拖后腿。

一个真实问题:GA4 的沉默

bfcache 恢复时,页面 JS 是被冻结的——gtag() 不会重新执行,Meta Pixel 不会重新 fire。所以 GA4 里的 page_view 事件会漏统计大约 20% 的移动端返回导航。这不是 GA4 的 bug,是 bfcache 的设计特性。

解决方案就是上面那几行 pageshow listener。WooCommerce 商家在 2025 年 Chrome 扩大 no-store 页面的 bfcache 覆盖范围后,吃过这个亏:服务器日志显示的访问量比 GA4 多出一截,找了很久才发现是 bfcache 导致的事件静默丢失。

常见阻止原因和修复方向

1. unload 事件监听器

这是最常见的根因。beforeunload 可以保留(用于敏感操作提示),但 unload 监听器建议全部移除,用 pagehide 事件替代。

// 旧写法(阻止 bfcache)
window.addEventListener("unload", () => {
  sendAnalytics();
});

// 正确写法(不阻止 bfcache)
window.addEventListener("pagehide", (event) => {
  if (event.persisted) {
    // 进入 bfcache,浏览器会在恢复时重新触发 pageshow
  } else {
    // 真正卸载,才执行清理
    sendAnalytics();
  }
});

2. 持有中的 IndexedDB 事务

如果页面有未完成的 IndexedDB 写操作,浏览器不会冒险缓存这个页面,因为恢复时事务状态会不一致。修复方式是在进入 bfcache 之前主动关闭或提交事务。

3. WebSocket / WebRTC 连接

持久的双向连接通常意味着页面状态不能被安全地冻结。不过这个是合理的业务需求,不能为了 bfcache 随意断开——这类页面的 bfcache 命中率本来就应该偏低。

4. 2025 年的好消息:Cache-Control: no-store 不再是障碍

Chrome 在 2025 年移除了 Cache-Control: no-store 响应头阻止 bfcache 的限制。这意味着之前因为静态资源策略配置而无法缓存的页面,自动变得更适合 bfcache 了。如果你的网站之前测过 bfcache 不行,现在值得重新跑一遍 DevTools 的 bfcache inspector。

Chrome DevTools 的内置检查工具

Chrome DevTools 有一个专门检查 bfcache 问题的面板,比 API 更直观,适合调试阶段用:

  1. 打开 DevTools → Application → Back/Forward Cache
  2. 点击「Test back/forward cache」
  3. Chrome 会自动导航到 chrome://terms/ 然后返回你的页面
  4. DevTools 直接告诉你:这次返回有没有走 bfcache,如果不走,原因是什么

建议先在这个工具里跑一遍你流量最高的页面(商品页、列表页、详情页),找到最大的瓶颈。

结论:bfcache 命中是可以优化的

bfcache 不是「浏览器自动会做的事,网站工程师不用管」——代码写法直接决定了你的页面能不能被缓存。对于移动端流量大的站点,每 5 次导航就有 1 次是返回,每提高 10% 的 bfcache 命中率,意味着大量用户每次点返回都能省掉一次完整的页面加载。

马上可以做的两件事:

  1. 打开 DevTools → Application → Back/Forward Cache,测试你流量最高的 3 个页面,找到最大的 bfcache 杀手
  2. 在你的分析代码里加上 pageshow listener,确保 bfcache 恢复时的访问不被漏统计

Chrome 的 NotRestoredReasons API 刚稳定,这个方向的诊断能力 2026 年会越来越成熟。现在先把最明显的 unload 监听器清掉,是成本最低、收益最直接的一步。

评论区

0 条评论

登录后可评论。

阿速·性能优化 11 阅读