点了三年返回键,今天才发现这 20% 的页面根本不是「快」——是浏览器偷偷从内存里捞出来的
你点返回键,页面秒开。你以为是网速快,其实不是。
Chrome 里大约 20% 的返回/前进导航,根本没有走网络请求——浏览器把整个页面状态快照存进了内存,你点返回的时候直接「解冻」,连解析 HTML、带解析 JS 都不用。
这个机制叫 bfcache(Back/Forward Cache)。它不是 HTTP 缓存,不是 Service Worker 缓存,而是浏览器进程里的整页内存快照。速度快到没有 LCP 可量,因为根本没有 Paint 在发生。
但问题来了:你的站点,很可能正在亲手破坏它。
unload 监听器:最大的罪魁祸首
第三方脚本装上监听器的成本极低,但伤害是真实的。Google 的数据显示,有 unload 监听器的页面,bfcache 命中率平均下降 18 个百分点。
unload 原本的意思是「页面即将卸载」,但对浏览器来说这意味着「不能安全地冻结这个页面」,因为 unload 代码预期一定会执行。实际情况更糟:移动端 unload 根本不一定触发,Chrome 优先保 bfcache 而跳过它——所以你装的这个监听器,既不能可靠执行,又把最快的一条路堵死了。
解法很简单:把 unload 换成 pagehide。pagehide 在页面进入 bfcache 之前触发,行为覆盖了 unload 的所有合法用途,且不会阻止缓存。
// Bad
window.addEventListener('unload', () => {
navigator.sendBeacon('/analytics/leave');
});
// Good
window.addEventListener('pagehide', (event) => {
navigator.sendBeacon('/analytics/leave');
});
Cache-Control: no-store:你以为在防过期,其实在堵高速
另一个高频踩坑点是 no-store 头。很多站点全站默认 no-store 来「保证不返回过期内容」,结果把 bfcache 也一起关了小黑屋。
2025 年 Chrome 做了一轮行为调整,在特定安全条件下允许 no-store 页面进 bfcache,但约束条件多,且是 Chrome 特有。如果你的页面有 Cookie 或认证状态变化,冻结快照会立刻被驱逐。
对大多数内容型页面,Cache-Control: max-age=0, must-revalidate 已经足够保证新鲜度,同时保留了 bfcache 资格。如果你的页面根本没有私人敏感数据,甚至可以直接用 max-age=60 或更短。
WebSocket、IndexedDB:连接不关,缓存不入
打开的 WebSocket 连接、进行中的 IndexedDB 事务、未关闭的 BroadcastChannel——这些在导航发生时如果还处于活跃状态,都会阻止 bfcache。
不是说不能用,而是要在 pagehide 时主动断开,在 pageshow 恢复。
window.addEventListener('pagehide', () => {
if (event.persisted) {
// 页面要进 bfcache,主动关闭连接
myWebSocket?.close();
}
});
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
// 页面从 bfcache 恢复,重新建立
initWebSocket();
}
});
怎么知道自己有没有踩坑?两个 API 直接告诉你
Chrome 123+ 内置了 NotRestoredReasons API,可以在代码里直接拿到哪些原因导致你的页面没有走 bfcache:
const nav = performance.getEntriesByType('navigation')[0];
const reasons = nav.notRestoredReasons;
console.log('bfcache 拦截原因:', reasons);
Chrome DevTools 也有图形界面:Application → Back/forward cache → Run Test,能列出所有 blocker 并给出修复建议。
另外一个自检方式:在你的分析脚本里监听 pageshow 的 event.persisted:
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
console.log('bfcache 命中——这次返回是内存恢复,没有网络请求');
} else {
console.log('bfcache 未命中——走了完整加载');
}
});
bfcache 到底影响多大?Yahoo! JAPAN News 的真实数据
Yahoo! JAPAN News 做过一次排查,把移动端的 unload 监听器全部替换为 pagehide,配合缓存头调整,结果:
- 每次会话 pageview 提升 9%
- 返回/前进导航的 LCP 大幅下降
- 用户留存上升,因为返回感觉「秒开」
这个改动几乎没有代码新增,主要是删。删掉一个 unload 监听器、一个 no-store 头,换来 10%~20% 导航路径的零延迟。
下一步:30 分钟排查清单
- 打开 DevTools → Application → Back/forward cache → Run Test,看结果
- 搜索代码库里所有
addEventListener('unload'——第三方脚本装的最常见 - 查 Response Header 里有没有
Cache-Control: no-store,确认是否真的需要 - 在 pageshow 里加
event.persisted判断,让你的分析系统区分「新鲜加载」和「bfcache 恢复」 - 如果接了 WebSocket/IndexedDB,在 pagehide/pageshow 做好连接的生命周期管理
bfcache 是浏览器自带的能力,你不需要装任何依赖。问题是很多旧代码和第三方脚本在悄悄把它关掉。自查一下,可能五分钟就能找到那个堵路的监听器,换掉它,那部分用户的返回键体验就直接从「重新加载」升级到「瞬间恢复」。
评论区
登录后可评论。