Lighthouse Studio エンジニアの西森です。
今回は弊社が運営しているゲーム攻略サイト「神ゲー攻略」の Core Web Vitals の各指標を 20〜80% 改善した取り組みを紹介します。
Core Web Vitals とは
Core Web Vitals とは、Google が提唱するページのユーザーエクスペリエンスに焦点を当てた指標のセットです。
具体的には以下の3つがあります。
- LCP: 知覚される読み込み速度。ページがどの程度速く読み込まれるか
- FID: インタラクティブ性。ページがどの程度サクサク反応するか *1
- CLS: 視覚的な安定性。ページがどの程度安定して表示できるか(レイアウトがガタガタしないか)
2021年から Google の検索結果に影響を与えるようになったことで話題になりました。
SEO 的な側面でももちろん重要ですが、サイトのパフォーマンスに由来するユーザー体験を測る上で重要な指標です。
CrUX API を使った Core Web Vitals の計測
Core Web Vitals 指標を計測できるツールはたくさんあります。
これらのツールはいずれも効果的な分析をするための便利な機能を提供しているのですが、計測数が限られているためアクセス規模によってはサンプリングをかける必要があったり、やりたいことに対してオーバースペックで高額であったりと、神ゲー攻略の計測には帯に短し襷に長しと言った状況でフィットするものが見つかりませんでした。
そこで見つけたのが CrUX API です。
CrUX は Chrome ユーザーが実際にサイト上で体験したパフォーマンスのメトリクスを収集したパブリックなデータセットで、CrUX API はそんな CrUX データセットからメトリクス情報を取得することができる API です。
以下のような特徴があります。
- CrUX データセットにあるデータはすべてパブリック(他の Web サイトの状況もチェックできます)
- 28日移動平均の数値が返ってくるので安定している(移動平均を取る分、改善の効果が完全に反映されるまでには時間がかかります)
- 無料!!
もちろん、こちらはメトリクスを取得できるだけなので、他のモニタリングサービスのように改善のためのインサイトを提供してくれたりはしません。
プロジェクトチームでは PageSpeed Insights や Lighthouse、その他各種ログ等でボトルネックの分析は十分行うことができていたため、この点は問題ないと判断しました。
データの安定性も十分、他のサイトとの比較も可能、そして無料ということで神ゲー攻略では CrUX API を計測基盤として据えることにしました。
具体的には以下のように Google App Script をデイリー実行して CrUX メトリクスを取得、Looker Studio で可視化し、デイリーレポートとして Slack へ通知する、という構成にしています。
冒頭でも紹介した、Looker Studio で可視化した一例がこちらです
サイト全体の傾向はこれで掴むことができますが、ページ個別の状況も知りたいときがあります。
そういったケースに対応するために CrUX History API を使って、オンデマンドに特定のページのメトリクスを調査できる Slack Bot も作りました。
体験の優れている他のサイトをベンチマークとして設定し、その水準に追いつくことをチーム全体で目標にして改善をすすめていきました。
Core Web Vitals の改善施策
計測の準備が整ったので、実際に改善を進めていきました。
施策をいくつか紹介します。
SPA + SSR から SSR only へのリアーキテクチャ
神ゲー攻略は初回読み込み時のページレンダリングを SSR で行い、以降のページ遷移は SPA で動作するハイブリッドなレンダリングを採用していました。 こちらのアーキテクチャについては Lighthouse Studio CTO の海老原が過去に紹介しているので良かったらご一読ください。
体験向上のために初回読み込み以降は SPA として動作するようにしていましたが、計測してみたところ SPA 関連のコード群が FID に予想以上に悪影響を与えていることや、長年のコードの蓄積によって非効率な処理がいくつかあり体験としても特に向上できていないことが分かりました。
体験面以外にも以前から課題がありました。
神ゲー攻略の SPA は lit-html 等を利用して独自に構築していますが、ユーザーとのインタラクションが発生するようなリッチなコンテンツを構築するには不向きであったため、そういったコンテンツはライブラリやエコシステムが充実している Vue や React で構築することが多いです。
独自 SPA の世界とリッチコンテンツの世界は別であるため、JavaScript のイベントを経由して通信することで連携を取っている状態でした。
SPA 側でディスパッチしたイベントをリッチコンテンツ側でリッスンしてイベントに応じた処理をしたりする必要があるわけですが、これは明示的にコード上で依存やルールとして存在している訳ではないため、ディスパッチしなくてもあるいはリッスンしなくても動いているように見えてしまうことがありました。
例えば、SPA 世界で起こるイベントの一つにページ遷移があります。
特定のページ限定で埋め込まれているようなリッチコンテンツはこのイベントをリッスンしてクリーンアップ処理(リソースの解放・コンポーネントの破棄・イベントリスナーの削除等)をする必要があり、忘れるとメモリリークが発生します。厄介なのは忘れてしまっても問題なく動いているように見えてしまう点です。
// ページ遷移イベントの受け渡しの例 // 独自 SPA システム側 document.dispatchEvent(new Event('spa_page_unload')); // リッチコンテンツ側 document.addEventListener('spa_page_unload', () => { // クリーンアップ処理 // 忘れるとメモリリークとなったり、残ったイベントリスナーが意図せず発火し続けたりしてしまう });
以上は一例ですが、このような機能追加時に守らないと不具合が起こる暗黙のルールのようなものが他にもいくつかある状況で、エンバグしやすいというパフォーマンス以外の課題が以前からありました。
計測の結果と以前からある課題を受けて SPA システム自体の改善も検討したのですが、根本から改善するには大幅に手直しする必要があることや、少なくとも SPA の改善より低コストで行うことのできる SSR only にすれば十分な改善効果が見込めることから SPA システムを最適化・再構築するのは今ではないと判断しました。
ハイブリッドなレンダリングを廃止することに決め、SSR 一本化することで FID 及び開発生産性を改善しました。
(SPA を完全に諦めた訳ではなく、必要性が高まったタイミングで再度検討したいと考えています)
画像領域の遅延確保
ゲーム攻略サイトという特性上、サイト内には画像が多く存在します。
CLS を改善する戦術の一つに画像要素の width / height を設定して領域を事前に確保しておくというものがありますが、神ゲー攻略ではシステムの構成上難しい事情がありました。
効率的な記事作成を行うため、神ゲー攻略では複数のデータソースから記事に関するデータを収集し、BFF で統合して記事を完成させる構成を採用しています。
BFF でデータが統合されたタイミングで最終的な記事が完成するため、事前に記事に含まれる画像を特定することが現実的に困難な状況でした。
記事が完成した後に画像サイズを計算していてはレスポンスが遅すぎます。
こうした制約があったためすべての画像を完全に領域確保するのは諦め、BFF で記事が完成したタイミングでキューへ画像取得タスクを流していき、裏でサイズ情報を徐々に取得していくアプローチをとりました。
サイズが取得済みの画像に関しては BFF でサイズを埋め込んでレスポンス、そうでない場合は領域確保は一旦諦めてキューへ流しワーカーへ依頼する構成です。
初めてレスポンスする際にはサイズ情報は含まれないため画像領域がガタついてしまいますが、最終的にはすべての画像がじわじわ領域確保されていくようになっています。
計測とゴール設定
効果が可視化されることで、施策の優先度を入れ替えたりやる必要があるか否かを決定しやすくなり効果的に改善を進めることができました。
トレードオフが発生するような施策においては、ゴールの達成に特に必要なさそうであれば冷静に外す決断もできています。
開発チームだけでなく記事の編集チームも含めた全体で合意をとってゴール設定をしたことで、終わりの見えない改善プロジェクトになってしまうことを防げました。
パフォーマンス改善のようなプロジェクトではやろうと思えばやれることは際限なくあるので、引き際・ゴールを事前に設定しておくことでいつまでも着手し続けてしまうことを防げます。
計測するだけではなく、その数値がどうあるべきなのかまでセットで考えることが大事だと思います。
おわりに
神ゲー攻略での Core Web Vitals の改善事例を紹介しました。
弊社では他にも Kaubel という商品比較メディアを運営しています。今回の経験は Kaubel の改善にも活かしていく予定です。
CrUX API は手軽に使えるので Core Web Vitals 計測の初めの一歩としてはとてもおすすめです。
この記事が皆さんの参考になれば幸いです。
ありがとうございました😊
*1:※ 2024年3月から INP に置き換わります https://developers.google.com/search/blog/2023/05/introducing-inp?hl=ja