BFCache 页面级缓存

BFCache(Back/Forward Cache)缓存整个页面运行现场,实现前进后退秒开。

#tech / dev / frontend #type / concept #status / evergreen #platform / browser

[!info] related notes

BFCache 页面级缓存

Back/Forward Cache 是浏览器为”前进/后退秒开”做的页面快照缓存。

缓存内容

BFCache 缓存的是整个页面运行现场,包括:

  • DOM 状态
  • JS 堆内存中的很多状态
  • 滚动位置
  • 表单输入内容
  • 某些事件监听器状态

和 HTTP 缓存的区别

对比BFCacheHTTP 缓存
缓存对象整个页面上下文资源响应(CSS/JS/图片)
存储位置内存Memory/Disk
触发条件前进/后退资源请求

效果

用户点击”后退”时:

  • 页面像没被销毁过
  • 几乎瞬间恢复
  • 不需要重新请求资源
  • 不需要重新执行 JS

换句话说,命中 BFCache 时,浏览器恢复的不是”某个资源缓存”,而更接近之前那份页面运行现场。

pageshow / pagehide 事件

pagehide

在页面即将被冻结进入 BFCache(或被卸载)时触发:

window.addEventListener('pagehide', (event) => {
  if (event.persisted) {
    // 页面将进入 BFCache(而非被销毁)
    // 可以在此清理资源
  }
});

pageshow

在页面从 BFCache 恢复(或首次加载)时触发:

window.addEventListener('pageshow', (event) => {
  if (event.persisted) {
    // 页面从 BFCache 恢复
    // 可以重新获取数据、重置定时器
  }
});

实际用途

  • BFCache 恢复时 JS 堆中的数据可能是过期的,用 pageshow + persisted === true 重新拉取
  • pagehide 时关闭 WebSocket 连接、清理定时器,避免阻止 BFCache

notRestoredReasons API

PerformanceNavigationTiming.notRestoredReasons(Chrome 110+)返回页面未被 BFCache 恢复的原因:

const entries = performance.getEntriesByType('navigation');
const navEntry = entries[0];
if (navEntry.notRestoredReasons) {
  console.log(navEntry.notRestoredReasons);
  // [{ reason: "unload-listener", src: "top-level" }, ...]
}

返回数组,每项包含:

  • reason:字符串,如 "unload-listener""open-idb-connection"
  • src:哪个 frame/origin 导致的

适合在生产环境监控 BFCache 命中率。

Cache-Control: no-store 与 BFCache

  • 历史行为:某些浏览器曾将 no-store 视为阻止 BFCache 的信号
  • 当前行为(Chrome 86+、Firefox、Safari):no-store 不阻止 BFCache
  • 原理:BFCache 是页面级快照,不是 HTTP 资源缓存,no-store 的语义不适用
  • 但注意:no-store 响应不会被 HTTP 缓存,页面从 BFCache 恢复后的新 fetch 请求仍然不会命中 HTTP 缓存

Chrome 实现细节

  • 进入 BFCache 时,Chrome 冻结页面的 JS 执行、定时器和网络活动
  • 内存限制:内存压力下 Chrome 会淘汰 BFCache 条目
  • 跨域 iframe:如果页面中的 iframe 是跨域且不允许 BFCache,整个页面可能被排除(行为因浏览器而异)
  • Clear-Site-Data: cache 头部清除 HTTP 缓存,但不影响 BFCache

不能进入 BFCache 的情况

以下因素会让浏览器认为”这个页面不适合被冻结恢复”:

  • unload 事件监听器(最常见的原因
  • 未关闭的 IndexedDB 连接
  • 活跃的 WebSocketWebRTCBroadcastChannel 连接
  • 运行中的 WebAssembly(某些浏览器,有可变全局变量时)
  • HTTP(非 HTTPS)页面(某些浏览器)
  • 某些生命周期处理
  • 不安全的资源占用
创建于 2026/4/7 更新于 2026/5/27