あなたのアナリティクススクリプトは、おそらく Back/Forward Cache を無効化している
back/forward cache は戻るボタンによるナビゲーションをほぼ瞬時にするが、たった 1 つの unload リスナーがページ全体でそれを無効化する。原因はたいてい計測スクリプトであり、CrUX は今やその損害を計測できる。
Web 上で最速のナビゲーションとは、何も読み込まれないナビゲーションだ。back/forward cache (bfcache) はまさにそれを実現する。そして、あなたのアナリティクスベンダーのスクリプトに含まれるたった 1 行が、サイト上のすべてのページでそれを無効化しうる。
bfcache はブラウザの最適化機能で、ユーザーがページを離れる際に、JavaScript ヒープも含めたページの完全なメモリ内スナップショットを保持する。戻るボタンを押すと、ブラウザはそのスナップショットを復元して実行を再開し、ネットワークリクエストなしでほぼ瞬時に読み込まれた状態を作り出す。戻る・進むのナビゲーションは全ナビゲーションの推定 10〜20% を占めるため、これはエッジケースではない。
リスナー 1 つでページ全体が失格になる
bfcache の対象資格は、設計上もろい。ブラウザは unload イベントリスナーを登録したページをフリーズしない。unload は、そのページが破棄されることを前提としていることを意味するからだ。デスクトップでは、Chrome と Firefox は unload リスナーを持つページをすべて bfcache 対象外にする。例外も部分的な救済もない。
そのリスナーはあなた自身のものである必要はない。ページ内、あるいはサブフレーム内から unload を登録するサードパーティスクリプトでも、トップレベルのドキュメントを失格にする。Lighthouse が専用の no-unload-listeners 監査を備えているのは、まさに問題のコードがサイト作成者の書いていないコードであることがあまりに多いからだ。
beforeunload はモダンブラウザでは失格要因ではなくなったが、信頼性が低く、ユーザーに未保存の変更がある場合を除いて引き続き避けるのが最善だ。
計測スクリプトが定番の犯人
unload イベントは、最後のビーコンを送る——セッションをフラッシュする、「ページが閉じられた」イベントを送る——古典的な場所だ。そのため、行動計測スクリプトや広告スクリプトは絶えずこれに手を伸ばす。
Facebook の fbevents.js は unload ハンドラを登録しており、HTTP Archive によれば全 Web ページの約 9% に出現する。PayPal のタグは unload イベントを追加する iframe を注入し、多くのチェックアウトフローで bfcache をブロックする。hCaptcha のようなサブフレームスクリプトも同じことをしてきた。これらのどれも、あなたのコストになり始めるのにあなた自身のコードへの変更を必要としない。ベンダーがアップデートを配信するだけで十分だ。
CrUX が今や請求書を見せてくれる
これはかつてフィールドデータでは見えなかった。2024 年 3 月のデータセット以降、Chrome User Experience Report (CrUX) は navigation_types の内訳——back/forward cache から提供された訪問の割合を含む——を報告するようになった。これにより、どれだけの実ユーザーが瞬時のパスを逃しているかを確認できる。
その相関は顕著だ。CrUX の分析では、高い back_forward_cache の割合と instant_lcp_density——LCP が 200 ms 未満の読み込みの割合——との間に強い統計的関係 (ρ=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 をリッスンする理由のない計測手段を使うことだ。プライバシーファーストのトラッカーは、たった 1 つの keepalive リクエストでページビューを記録し、リクエストがページよりも長く生き残るのをそのまま任せる——破棄時のビーコンも、unload ハンドラも、ブラウザがフラグを立てるものも何もない。
fetch('/collect', {
method: 'POST',
keepalive: true,
body: JSON.stringify({ site_id, path: location.pathname })
})
トラッカーがページのバックグラウンド化に反応する必要がある場合、正しいシグナルは visibilitychange または pagehide であり、どちらもページを bfcache 対象外にすることなく発火する。Monoid のトラッカーは約 2 KB で、Cookie を一切設定せず、ページのライフサイクルではなく SPA のルート変更のために history.pushState をフックする——だから登録すべき unload リスナーはなく、あなたの back/forward cache を無効化するものは何もない。
監視型アナリティクスは、ラボ実行には現れない形でパフォーマンスに税を課す。bfcache ブロックはそのなかでも最も静かなものの 1 つだ。エラーもなく、遅いレンダリングもなく、ただ、メモリから復元すべきだった戻るボタンがネットワークから再読み込みするだけだ。
Comments
Loading comments…