こんにちは、第11/12代黒帯(ヤフー内のスキル任命制度/Webフロントエンド領域)の浜田(@narirow)と、Yahoo!ニュースのエンジニアの喜楽(@gladenjoy)です。今回は、ヤフーで対応をすすめてきた、BFCacheの有効化にむけた取り組みについてお話しします。BFCacheとはそもそも何か、有効化のボトルネックには何があるか、そして調査方法などの具体的な手法などを交えてご紹介します。
ヤフーではこれまでスムーズなユーザー体験を実現するべく、高速にページを表示できるBFCache技術の検証と、BFCacheの有効化率を上げる取り組みを行ってきました。その結果、Yahoo!ニュースでの検証では、PVが+2%向上、広告売上が+9%向上する結果となり、ユーザー体験だけではなくビジネスKPIも向上する結果を出せることがわかりました。現在は、広範囲への展開を目指して引き続きプロジェクトを実施中です。
BFCacheとは
BFCache(バックフォワードキャッシュ)は、ブラウザでページを遷移した時に、ページの完全なスナップショットとして保存されるメモリキャッシュを指します。通常遷移する場合はページが0から読み込み直されるに対し、BFCacheが有効の場合はページ全体のスナップショットから復元され、JavaScriptもそこから再開されます。元見ていた画面がそのままブラウザ上に復元される形で表示されるので、通常の遷移と比較して高速に表示されます。
なぜBFCacheが重要なのか
1. アプリケーションの実装に依存しないパフォーマンスの最適化が可能
パフォーマンスの高速化をするためには、アプリケーションの設計や実装、レンダリング手法を見直すことで最適化を図るのが一般的です。一方で、BFCacheの有効化によってブラウザ内の最適化を行えれば、通常アプリケーションの設計そのものには影響を及ぼしません。各サービスが大幅な改修コストをかけることなく、ブラウザの最適化によって内部的に高速化の恩恵を享受できるのです。
2. スクロール位置の復元によるUXの向上
ブラウザバックを行った際にスクロール位置が戻らず、不便に感じたことは無いでしょうか?「もっと見る」などの逐次読み込みが行われているページでは、スクロール位置を開発者が保持して適切に復元処理を行う必要があります。BFCacheが有効になれば、スナップショットからページが復元されるので、特殊な処理を行うことなくスクロール位置は元見ていた場所に戻ります。
3. ユーザーの多くがブラウザの戻る/進むを利用しており影響が大きい
ヤフーでは、多くのユーザーがブラウザの戻る/進むを利用していることがわかっています。
この割合は、ページの特性やデバイスによっても変化しています。一覧ページからコンテンツを参照しているシーンでは、詳細ページを閲覧後にまた元の一覧に戻る可能性が高いです。また、PCよりもモバイル端末のほうが戻る処理を実行しやすいといった、ユーザー行動特性が存在します。ヤフーには、このような特性を持ったサービスが多く存在しています。
月間840億回以上のページが表示される(※)ヤフーのサービスにおいて、戻る/進むの遷移が高速化された場合、サービスとして大きなUXの改善につながることが想定されました。
※:ヤフー株式会社自社調査2021年7月~12月の月平均(タブレット、従来型携帯電話除く)
BFCacheの有効化がなぜ難しいのか
BFCacheによる影響は大きいことがわかりましたが、なぜ有効化が難しく、慎重な検証が必要なのでしょうか? 大きく2つの理由があります。
理由1. ブラウザ内の適用条件が複雑である
これまでブラウザに実装されてきたWeb標準技術の互換性や、セキュリティの問題によって、特定のケースで有効化できない場合が存在します。以下は、ChromiumのソースコードからBFCacheが無効化される要因を抜き出したものですが、ゆうに50以上もの無効化されるケースが存在します。
unloadイベントハンドラの存在
その中でもとくに影響を及ぼしているのは、unloadイベントハンドラの有無です。以下のコードは、unloadイベントハンドラを用いた処理の例です。
// unloadイベントハンドラは特定のブラウザでBFCacheの動作を阻害する
document.addEventListener("unload", () => { /* ページ離脱時の処理などを記述 */ });
Webではこれまで慣習的に「ページの離脱時をハンドルする手段」「Safariとの挙動を揃えるためのハック的な手段」として、unloadイベントハンドラを利用してきました。このような処理が、ページのサブフレーム含め一つでも含まれていると、Mozilla FirefoxやChromiumブラウザでは、ページ全体のBFCacheが有効化されません。
Google Chromeのデータによる統計では、2022年の時点で約57%のページでunloadイベントハンドラが使用されていることがわかっています。
unloadイベントハンドラの使用は現時点で非推奨となっており、ページの離脱タイミングをハンドルする際はpagehideイベントハンドラを利用する必要があります。
Cache-Control: no-store
ヘッダーの存在
キャッシュの設定によっても、BFCacheが有効化されないブラウザがあります。Webページがブラウザにキャッシュの扱いについて伝えるCache-Controlヘッダーには、no-storeというオプションがありますが、この値はブラウザがページのデータをキャッシュしないことを示しています。
しかし、現在のSafari(Safari 16)においては、この値が付与されていてもBFCacheが有効になります。一方で、Chrome(Chrome 114)においては、この値が付与されているとBFCacheが無効化されます。
ブラウザのBFCache挙動の差異
上述した2件だけでも、ブラウザ別に挙動が異なることがわかったと思います。今回の検証では、さまざまなブラウザの挙動を検証して全社への影響を見極める必要がありました。
調査した結果、ブラウザやデバイス(PC/Mobile)の組み合わせによって、ブラウザのBFCacheの挙動は大きく異なっています。以下に、社内でテストした結果をまとめます。
理由2. 実装済みのアプリケーションログへの影響
すでに実装済みのアプリケーションの動作についても、少なからず影響が発生します。とくに影響が大きいのは、ページの表示ログや広告です。
再読み込みせずスナップショットから復元されるという特性によって、ページの表示時点のログ(PVログ)が落ちなくなるので、これに付随したログの値は計算上おかしくなります。たとえば、ページを表示したユーザーがクリックしてページ遷移した割合(クリック率)を求めるとします。BFCacheが無効の場合は、1回のページ表示に対して最大1回の遷移が発生することが仮定できます。一方で、ブラウザの戻る/進むでBFCacheが有効の場合は、スナップショットから復元されるためページの表示ログが落ちない状態で再度遷移できることになります。この挙動によって、クリック率が変化してしまうのです。
ヤフーにおける対応方針
私たちはこれらの課題の解決に向けて、以下のような方針をたて、施策を実施しました。
方針1. BFCacheが有効化された場合の影響を検証する
ユーザーの挙動による変化によるログや広告への影響が大きいため、サービスで慎重に確認を行ったのちに全社適用を進める方針を取りました。まずは、多くの利用ユーザーがいる「Yahoo!ニュース」においてA/Bテストによる検証を行い、重要なサービスメトリクスに影響が無いかを確認しました。結果については後述します。
方針2. BFCache有効化の実装条件を整える
BFCacheにはさまざまなWeb技術が影響を及ぼしますが、とくに顕著に影響するunloadイベントハンドラを洗い出して、削除または代替処理に置き換える対策を全社で実施しました。どうしても手がいれられない場合は、無効化されてもサービスの影響が無いことを確認しています。
また、BFCacheが有効化した場合、ログの変化によって、サービスの重要なキーメトリクスに影響を及ぼす可能性があります。こちらも全社的に確認をとり、重要なサービスでは処理をpageshow/pagehideイベントハンドラによる処理に変更し、正確にユーザーのページの再表示を補足できるよう修正を行いました。
上記の確認が終わったことで、Permissions-Policyによるunloadイベントハンドラの無効化を適用できる条件が整います。
Permissions-Policyによるunloadイベントハンドラの無効化は、該当のページのレスポンスに以下のヘッダーをつけることで、unloadイベントハンドラを強制的に無効化するドラフト段階のWeb APIです。この技術を利用すれば、BFCacheの適用率をサービスのコントロール範囲で適用範囲を広げていくことが可能となります。このAPIは、Chromeでは実装が進められており、ニュースの実証試験でも利用しています。
// unloadイベントハンドラを完全に無効化するPermissions-Policyヘッダー
Permissions-Policy: unload=()
BFCacheの確認方法
複雑な適用条件をもつBFCacheの挙動をどのようにして確認すればいいでしょうか。以下に紹介する方法を使えば、BFCacheから復元されたかどうか、なぜBFCacheから復元されなかったかを確認することができます。
確認1. BFCacheにより復元されたかを確認する
ブラウザの戻る/進むを行った際のページ表示が、BFCacheから復元されたものなのか、または再度読み込み直されたかを区別するには、以下のようにpageshowイベント
のevent.persisted
のフラグがtrueであるかを確認することで判断ができます。
window.addEventListener("pageshow", (event) => {
if (event.persisted) {
// BFCacheから復元された場合
} else {
// BFCacheから復元されなかった場合
}
});
注意したいのは、pageshowイベントはブラウザバックフォワードナビゲーションを伴わないページ表示時にもイベントが発火する点です。
ページの表示がブラウザバックフォワードのナビゲーションによるものかは、PerformanceNavigationTiming APIを使って以下のコードで判定できます。
const entries = performance.getEntriesByType("navigation");
entries.forEach((entry) => {
if (entry.type === "back_forward") {
// バックフォワードナビゲーションによりページが表示されたとき
}
});
これらの値をアナリティクスツールなどに連携しておくと、BFCacheから復元されたページ表示数/ブラウザバックフォワードナビゲーションによるページ表示数
を算出できるようになり、この数値を社内では「BFCache有効化率(※)」と呼んでいます。なお、Yahoo!ニュースにおけるBFCache有効化率は最大80%程度になることもあります。有効化率はユーザー体験向上の一つの指標にできるでしょう。
この方法はWeb標準で定義されており、クロスブラウザで確認の方法として利用できます。
※:PerformanceNavigationTiming APIにおけるentry.type
は、ブラウザ再起動した場合・タブを複製した場合・タブをクローズした直後にタブを復活させた場合にもback_forward
が設定される可能性があります。そのため、この記事の中で述べられているBFCache有効化率は、正確なBFCacheのヒットレートとは異なる参考値となります。正確なBFCacheのヒットレートを算出する場合は、以下のページを参考にしてください。
確認2. なぜBFCacheから復元されなかったかを確認する
BFCacheTester
Chromeの開発者ツールに搭載されているBFCacheTester
は、BFCacheの動作をテストし、BFCacheが有効となりうるか、ならない場合はどのような原因があるかを明らかにするツールです。
利用方法はChromeの開発者ツールを開き、Application
タブ内にあるBack/forward cache
のリストを選択、Test back/forward cache
ボタンをクリックすることで検証することができます。このとき、事前にバックフォワードを伴うページナビゲーションをユーザーが行う必要はなく、ただボタンを押すだけで検証を実行できます。
以下は実行結果の例です。
上記の例ではメインフレーム・サブフレーム共にunloadイベントハンドラが含まれていると表示されているため、unloadイベントハンドラがBFCacheの有効化を阻害していることがわかります。
BFCacheTesterは、あくまでもChromeにおけるBFCache有効化を阻害する要因がないかを示すものであることは注意です。たとえば、BFCacheTesterでBFCacheの阻害要因がある表示されたとしても、他のブラウザやデバイスではBFCacheが有効になりうることも十分ありえます。
Not Restored Reasons API
BFCacheが有効にならなかった原因を明らかにするときに便利なAPIとして、Not Restored Reasons APIがあります。このAPIを使用すれば、BFCacheの動作を阻止する可能性のある問題を特定し、それらを解決するための洞察を得ることができます。
先ほどのChromeのBFCacheTesterはローカル環境でのみテスト可能ですが、NotRestoredReasonsAPIは本番環境にも組み込めることから、実際のユーザーからのアクセス時になぜBFCacheから復元されなかったかを明らかにするときに有益でしょう。
以下はコードの例と実行例です。
const navEntries = performance.getEntriesByType("navigation");
for (let i = 0; i < navEntries.length; i++) {
console.log(`Navigation entry ${i}`);
const navEntry = navEntries[i];
console.log(navEntry.notRestoredReasons);
}
実行結果(BFCacheから復元できなかったときの例)
{
"blocked": true,
"src": null,
"id": null,
"name": null,
"url": "<URL>",
"reasons": [
"MainResourceHasCacheControlNoStore",
"Internal error",
"Unload handler"
],
"children": [
// (省略)
]
}
実行結果(BFCacheから復元できたときの例)
{
"blocked": false,
"src": null,
"id": null,
"name": null,
"url": "<URL>",
"reasons": [],
"children": [
// (省略)
]
}
ただし、originが異なるリソースの場合、セキュリティの関係で原因が取得できないことには注意が必要です。Yahoo!ニュースではこうした理由により本APIによる検証を実施しませんでしたが、結果をモニタリングできれば、BFCacheの有効化率を向上させるために何をすればよいか、直接的な原因が明らかになるでしょう。
なお、このAPIは2023年7月現在OriginTrialステータスのため、利用するには設定が必要です。また各ブラウザで標準化されているAPIではないため、Chromeでのみ利用できます。
Yahoo!ニュースでの実証実験
さて、ヤフーでは前述の方針をもとに、上述したテスト手法を駆使して、実際にYahoo!ニュースにおいてBFCache有効化率をどの程度まで向上できるかをテストしました。テストの条件は以下の通りです。
項目 | 条件 |
---|---|
対象デバイス | PC/スマートフォン |
対象ブラウザ | Chrome(テスト時点での最新バージョンは112)、スマートフォンはAndroid Chromeのみ対象 |
対象ページ | 記事詳細(記事本文を掲載しているページ) |
テスト期間 | 2023/04/12 ~ 2023/04/26 |
テスト対象のブラウザはChromeのみとしました。これはChromeがドラフト段階のPermissions-Policy: unload()
をすでに実装しており、unloadイベントハンドラを無視できることにより、正確なA/Bテストが実施できる唯一のブラウザだからです。(Permissions-Policy: unload()
はChromeのみで利用可能であり、Safariはデフォルトでunloadイベントハンドラを無視しているため、比較するA/Bテストが行うことができません。)
事前検証の結果、Yahoo!ニュースでは、Cache-Controlヘッダーからno-storeを削除すること、unloadイベントハンドラを削除することの2点の問題を解消できれば、BFCache有効化率が上がりそうということがわかっていたため、これらの設定を変更したときの数値を比較しています。
PC のテスト条件
ヘッダー | control | test | 備考 |
---|---|---|---|
Cache-Control | private, no-cache, no-store, must-revalidate |
private, no-cache, must-revalidate |
control からno-store を削除 |
Permissions-Policy | 未設定 | unload() |
スマートフォンのテスト条件
ヘッダー | control | test | 備考 |
---|---|---|---|
Cache-Control | private, max-age=15 |
private, max-age=15 |
controlのCache-Controlヘッダーにno-store の記載がないため変更なし |
Permissions-Policy | 未設定 | unload() |
上記の条件で A/B テストを実施した結果は以下の通りでした。
デバイス | BFCache 有効化率(control→test) | PV(control比) | 広告売上(control比) |
---|---|---|---|
PC | 0.02% → 47.30% (+47.28 points) | +0.65%(有意差なし) | +0.6% |
スマートフォン | 0.04% → 54.07% (+54.03 points) | +2.26%(有意差あり) | +9.0% |
まずBFCache有効化率ですが、Controlではほぼ0であったところからPC/スマートフォン共に50%前後まで大幅に増加することができました。
また、PCはPVに有意差がありませんでしたが、スマートフォンについては、PVは2%以上、広告売上は9%向上するという結果が得られました。
スマートフォンについて詳細に調べると、記事本文下の回遊用記事リストの部分のクリック数が増えており、その影響でPVが増加しました。
回遊用記事リストはSSR(サーバーサイドレンダリング)は行っておらず、記事リストをブラウザからAPIリクエストを行って取得し、CSR(クライアントサイドレンダリング)が行われています。BFCacheから復元されたときは、これらの記事リストの取得のネットワークリクエストがないので、クライアントサイドでのレンダリングコストが低減され、さらにスクロール位置の復元もBFCache無効時に比べて素早くおこなわれます。こうした要因によって、クリック数の増加につながったものだと考えています。
以下は、ブラウザバック時にBFCacheから「復元されたとき」と「されなかったとき」それぞれで、どのように表示スピードに影響したかを表した例です。(10fpsで、ブラウザバックしてからの表示の推移を表した図で、Chrome DevTool上で、CPUを4x slow downに設定しています)
BFCacheから復元された場合は、ブラウザバックすると即座に元のページのスクロール位置を復元された状態で表示され、クリックしたリンクがvisited表示になるまででも0.3秒しかかかっていません。一方で、BFCacheから復元されなかったときは、ブラウザバックからスクロール位置の復元までは3.3秒もかかっていることがわかります。
今回の検証において、BFCache有効化率を向上させることで、ユーザー体験を向上させるだけではなく、ビジネス指標まで向上することができたことは、大きな成果です。
また、PCよりもスマートフォンのほうが、BFCacheを有効化させるメリットがあるということが、今回の結果からはわかりました。
今回はChromeを対象にテストを行いましたが、以前Safariでも同様のテストを行っており、その際にも同様にスマートフォンでPV/UBが+2%以上向上する成果を得られています。
ブラウザバック時の表示を最適化するYahoo!ニュースの取り組み事例#BFCacheを有効にする
今後にむけて
今後、Yahoo!ニュースでの成果をもとによりよいユーザー体験を多くのページで実現できるよう、ヤフー全体でBFCacheの有効化を広げていく方針です。現在、Chromiumの開発コミュニティでは、BFCacheの適用条件を緩和していく動きがあり、Cache-Contorl: no-store
の有無に関わらずBFCacheを有効化する提案や、unloadイベントハンドラ
を廃止にする提案についても議論がなされています。また、直近でリリースされるChrome 115には、上述したPermissions-Policyヘッダーによるunloadイベントハンドラの無効化が搭載される予定となっています。これを利用すれば、ヤフーでも大きくBFCache有効化率を上げることができます。今後は、サービスの状況やブラウザの動向を鑑みながら、徐々に適用率を上げられるように展開を進めていく計画を立てています。
ヤフーでは引き続き、快適な操作性をスタンダードとして提供できるサービス・組織を目指し、まい進していきたいと思います。また少し速くなったYahoo! JAPANを、引き続きよろしくお願いいたします。
(2023年7月26日追記)
この記事の内容が「web.dev」にて紹介されました。
How Back/forward Cache Helped Yahoo! JAPAN News Increase Revenue by 9% on Mobile(外部サイト)
こちらの記事のご感想を聞かせください。
- 学びがある
- わかりやすい
- 新しい視点
ご感想ありがとうございました