返回博客

你的分析脚本很可能正在禁用前进/后退缓存

前进/后退缓存让后退按钮的导航近乎瞬时,但只要一个 unload 监听器就能让整个页面失去这项能力。罪魁祸首通常是跟踪脚本——而 CrUX 现在能衡量它造成的损失。

网络上最快的导航,是那种什么都不用加载的导航。前进/后退缓存(bfcache) 提供的正是这种体验——而你的分析厂商脚本里的一行代码,就能把它在你站点的每个页面上关掉。

bfcache 是一项浏览器优化:当用户离开页面时,它会在内存中保留整个页面的完整快照——包括 JavaScript 堆。按下后退按钮,浏览器会恢复这个快照并恢复执行,从而产生近乎瞬时的加载,且不发起任何网络请求。据估算,后退和前进导航占到 所有导航的 10–20%,所以这绝不是边缘情况。

一个监听器就让整个页面出局

bfcache 的资格判定在设计上就很脆弱。浏览器不会冻结一个注册了 unload 事件监听器的页面,因为 unload 意味着这个页面预期自己会被销毁。在桌面端,Chrome 和 Firefox 会让任何带有 unload 监听器的页面失去 bfcache 资格——没有例外,也没有部分通过这一说。

这个监听器甚至不必是你自己注册的。从你页面内部(甚至是某个子框架内部)注册 unload 的第三方脚本,会让顶层文档丧失资格。Lighthouse 专门提供了一项 no-unload-listeners 审计,正是因为肇事代码往往是站点作者从未写过的代码。

beforeunload 在现代浏览器中已经不再导致出局,但它并不可靠,除非用户有未保存的更改,否则仍然最好避免使用。

跟踪脚本是惯犯

unload 事件是发送最后一个信标的经典位置——刷新一个会话、发送一个“页面已关闭”事件——因此行为跟踪脚本和广告脚本总是离不开它。

根据 HTTP Archive 的数据,Facebook 的 fbevents.js 注册了一个 unload 处理器,并出现在大约 9% 的网页 上。PayPal 的标签会注入一个添加 unload 事件的 iframe,从而在许多结账流程中阻断 bfcache。像 hCaptcha 这样的子框架脚本也做过同样的事。这些都不需要你改动自己的代码就会开始让你付出代价——厂商推送一次更新就够了。

CrUX 现在把账单摆给你看

这件事过去在现场数据里是看不见的。从 2024 年 3 月的数据集开始,Chrome 用户体验报告(CrUX) 提供了一项 navigation_types 拆分——其中包括从前进/后退缓存提供服务的访问占比——于是你可以看到有多少真实用户错过了瞬时路径。

这种关联非常鲜明。CrUX 分析发现,较高的 back_forward_cache 占比与 instant_lcp_density(LCP 低于 200 毫秒的加载占比)之间存在很强的统计关系(ρ=0.87)。在 Google 于 2026 年 3 月的更新提高了 LCP、INP 和 CLS 的排名权重之后,一次自我招致的 bfcache 阻断会在第 75 百分位上成为可衡量的劣势,而不是一个可以忽略的舍入误差。

检测与修复

打开 DevTools,进入 Application → Back/forward cache,然后点击 Run Test。Chrome 会列出每一条阻断原因,包括由第三方添加的 unload 处理器。

要在你自己的代码中检测 bfcache 恢复,永远不要使用 unload。请使用 pageshow 并检查 persisted

window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    // restored from bfcache — no fresh page load fired
  }
})

要阻止第三方让你失去资格,请设置一个完全禁止 unload 监听器的响应头:

Permissions-Policy: unload=()

这会让来自任何脚本的 unload 处理器失效——无论是厂商标签、扩展程序,还是你自己的遗留代码——从而无论加载了什么,页面都保持具备 bfcache 资格。

从不碰它的跟踪器

结构性的修复方法,是使用一种压根没有理由去监听 unload 的埋点方案。一个隐私优先的跟踪器只用一个 keepalive 请求记录一次页面浏览,并让该请求自行在页面消失后继续存活——没有销毁信标,没有 unload 处理器,没有任何会被浏览器标记的东西:

fetch('/collect', {
  method: 'POST',
  keepalive: true,
  body: JSON.stringify({ site_id, path: location.pathname })
})

当跟踪器需要对页面被切到后台做出反应时,正确的信号是 visibilitychangepagehide,二者触发时都不会让页面失去 bfcache 资格。Monoid 的跟踪器大约只有 2 KB,不设置任何 cookie,并且为了应对 SPA 路由变化而挂钩 history.pushState,而不是页面生命周期——因此它没有任何 unload 监听器需要注册,也没有任何东西会禁用你的前进/后退缓存。

监控式分析会以一些在实验室运行中根本不会显现的方式对性能征税。bfcache 阻断就是其中最安静的一种:没有报错,没有渲染变慢,只是一个本应从内存恢复、却改为从网络重新加载的后退按钮。

来源

Comments

Loading comments…