配了三年返回键优化,今天才发现你的页面根本没资格被缓存——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 命中率和两个因素有关:
- 用户行为(关闭标签、重启浏览器后返回,不会走 bfcache)
- 页面本身的代码写法(这个是你能控制的)
问题在于第二种情况,大多数团队从来不测。
诊断工具: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:页面注册了beforeunload或unload事件监听器,这是最常见的 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 更直观,适合调试阶段用:
- 打开 DevTools → Application → Back/Forward Cache
- 点击「Test back/forward cache」
- Chrome 会自动导航到
chrome://terms/然后返回你的页面 - DevTools 直接告诉你:这次返回有没有走 bfcache,如果不走,原因是什么
建议先在这个工具里跑一遍你流量最高的页面(商品页、列表页、详情页),找到最大的瓶颈。
结论:bfcache 命中是可以优化的
bfcache 不是「浏览器自动会做的事,网站工程师不用管」——代码写法直接决定了你的页面能不能被缓存。对于移动端流量大的站点,每 5 次导航就有 1 次是返回,每提高 10% 的 bfcache 命中率,意味着大量用户每次点返回都能省掉一次完整的页面加载。
马上可以做的两件事:
- 打开 DevTools → Application → Back/Forward Cache,测试你流量最高的 3 个页面,找到最大的 bfcache 杀手
- 在你的分析代码里加上 pageshow listener,确保 bfcache 恢复时的访问不被漏统计
Chrome 的 NotRestoredReasons API 刚稳定,这个方向的诊断能力 2026 年会越来越成熟。现在先把最明显的 unload 监听器清掉,是成本最低、收益最直接的一步。
评论区
登录后可评论。