ブログに戻る

あなたのアナリティクススクリプトは、おそらく 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.jsunload ハンドラを登録しており、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…