こんにちは、第11代黒帯(ヤフー内のスキル任命制度/Webフロントエンド領域)の浜田(@narirow)です。今回はヤフー全社で実施してきた、「Webパフォーマンス改善プロジェクト」についてお話ししたいと思います。
長期に渡る活動の結果、多くのサービスのWebパフォーマンスが徐々に向上しています。この記事では、取り組みの経緯や、多くのサービス分析を通してわかったコスパの良い施策(比較的簡単に実施できてスコアも上がりやすい施策)などをご紹介します。
全社横断でWebパフォーマンス改善を実施する経緯
さかのぼること2021年、Googleから以下のような案内がありました。
「Core Web VitalsがGoogle検索の検索順位にも影響する指標となる」
この発表は、文字通り検索順位に影響する変更に間違いありませんが、それ以上に今後のWebサービスは「快適に操作できること」が当然のように求められていく時代になる転換点であるとも捉えていました。
現在、ヤフーの提供するWebサービス数は優に50を超えています。
ヤフーの代表的なサービスに絞ってCore Web Vitalsの数値状況を調査を実施したところ、いくつかのサービスではスコアの低い状態が常態化していたり、ベンチマークとしていた他社サービスと比較して劣後している現状がみえてきました。
待ち時間なく、すぐに目的を達成できる操作性を当たり前のように提供したい。ヤフーのように毎日多くのユーザーに繰り返し利用されるサービスであるからこそ快適性を求められるのはなおさらのことです。
このような背景から、全社の取り組みとして「Webパフォーマンス改善プロジェクト」を始動することにいたしました。
どのようなことを実施してきたか?
“全社”となると規模が大きくなり、そのやり方も含めて検討しなければなりませんでした。プロジェクトでは、以下の4つの施策を準備し実施しています。
- サービスのCore Web Vitalsスコアの可視化
- ナレッジの横展開
- 各サービスへの改善依頼
- 共通プラットフォームへの改善依頼
1. サービスのCore Web Vitalsのスコアの可視化
始めていくにあたって、まずは現在の状況を知ることが必要です。プロジェクトでは、改善のナレッジを集約するポータルを作成し、スコアやナレッジを集約していくことにしました。
可視化しなければならないパフォーマンスデータは、ラボデータと、フィールドデータの二種類があります。
- ラボデータ
- 説明: 特定の試験/ネットワーク環境下で、取得されたパフォーマンスデータ。
- 特徴: ネットワークやUI/UXをさまざまな観点から分析したり、特殊な環境下のテストするなど、詳細分析をすることに向いています。実際のユーザーが体験した数値ではないので、発生している問題を捉えきれない可能性があります。
- フィールドデータ
- 説明: 実際のアプリケーションで、使用したユーザー環境で取得されたパフォーマンスデータ。
- 特徴: 実際のユーザーからのデータであり、ビジネス指標とも結びつけて集計が可能です。すべての行動をロギングすることはできないので、詳細な分析には不向きです。
それぞれのデータで特性が異なっており、プロジェクトでは二種類の可視化を実施することにしました。
ラボデータの可視化
ラボデータの可視化は、lighthouse-ciを用いて実施しました。
lighthouse-ciは、Core Web Vitalsのスコア集計を定点観測することに特化した可視化ツールです。集計コマンドに加えて、集計されたメトリクスを時系列で管理するためのダッシュボード機能を備えています。OSSとして提供されており、セルフホストすることが可能です。
プロジェクトのポータルにアクセスすると、各ヤフーサービスのCore Web Vitalsのスコア情報を誰でも見られるようになっています。
lighthouse-ciでは、以下のような簡単なコマンドをcronで定期実行しています。lighthouseで対象ページのスコアを複数回サンプリングしたのち、その結果をサーバーに送信します。
// 各ページのメトリクスを収集
lhci collect --url="[ページURL]" --additive --settings.preset=desktop --settings.chromeFlags="--no-sandbox --disable-gpu --disable-dev-shm-usage"
// サーバーに送信
lhci upload --token="[トークン]" --serverBaseUrl="[サーバーのURL]"
ヤフーでは、1サービスあたり複数のページを対象としており、合計200を超える集計のエンドポイントに対して毎日計測を実施しています。
単純に計測しただけでは、先程述べたベンチマークとの比較検証がしにくい状態です。そこで、lighthouse-ciで取得されたデータを加工(ETL)し、ヤフーサービスとベンチマークサービスのスコアを重ねて週単位の推移で確認できるダッシュボードも提供しています。
上記により、日々のリリースで悪影響が出ていないかの確認に加え、ベンチマークサービスの改善状況と比較した形でスコアを知ることができるようになりました。
フィールドデータの可視化
フィールドデータの可視化は、Chrome User Experience Report (以下、CrUX)を用いて、全サービス比較可能なダッシュボードを作成しています。
CrUXでは、Chromeを使用する実際のユーザーから匿名で送信されたデータを統計的に集計することができます。パフォーマンスに関するメトリクスは、現在のところオリジン別かつ月次の単位でのみ取得できます。lighthouseのようなラボデータと異なり詳細なデータは取得できないものの、実際のユーザーが体感しているリアルなスコアを知ることができ、サービス全体の傾向を把握する用途に優れています。
プロジェクトでは、Core Web VitalsのキーメトリクスであるLCP/FID/CLSをドメイン別に集計し、CrUXのGoogleデータポータルの機能を使用して可視化しました。
作成したダッシュボードでは、フィルタ機能を備えています。上部のノブを操作することで、数値の悪いサービスのみを可視化できます。ラボデータではページごとの詳細な課題を見つけ、フィールドデータでは改善した結果を確かめると言った流れで確認するのが一般的な流れです。
目標値の策定
継続的にすすめるには、どこまで到達するべきか目標が定まっていることも重要です。私たちはあるべき状態を以下のように定義しました。
- Core Web Vitalsの基準で、”良好”のスコアが維持できていること
- 同種の他社サービスをベンチマーク(比較対象)として、優位なスコアであること
特徴的なのは、他社と比較するベンチマークを設けたことです。比較優位なユーザー体験を提供したいという思いから、このような定性的な観点も盛り込んだ目標値を設定しました。
2. ナレッジの横展開
本取り組みに先立って、いくつかのサービスで先行してパフォーマンス改善の取り組みを独自に実施してきました。ニュースの改善についての記事や、GYAO!の改善についての記事では、その取り組み内容をお話ししました。
これらのサービスは、離脱率や広告の売上といったビジネス指標と紐付けた計測も実施しています。ここで得られた知見は、ナレッジとして他のサービスにも適用できる内容が数多くあります。プロジェクトでは、こうして得られた知見を社内ナレッジとしてドキュメント化しています。
この中の施策のうち、比較的簡単に実施できかつ効果が高いものは、ピックアップして優先度の高い事項として全社に案内しています。(後述)また、独自の社内ナレッジとして、社内CDNを活用したキャッシュの最適化方法、社内サービスを応用したWebP画像の配信方法など、社内の連携を高める内容も含めて記載し、実践的な案内を心がけています。
3. 各サービスへの改善依頼
さて、パフォーマンス可視化環境、改善ドキュメントは作りましたが、問題は実際のサービスにどうやって施策を実施してもらうかということです。
単にサービスに課題だけ依頼した場合、ボトルネックを調査するだけでも時間がかかります。また、専門的な知識が要求されたり、他部門にまたがった改善を実施しなければならないこともあるでしょう。ビジネスと並行して改善を実施するには、単独で依頼するのが難しい状態でした。
こうした背景から、プロジェクト側でサービスに対しソースコードレベルで分析を実施し、優先度別に対策を提案することにしました。
各サービスに分析対象のページを取り決めてもらい、そのページのソースコードをプロジェクトメンバーが解析します。ボトルネックを見つけたら、「課題」「改善方法」「影響する指標(LCP/CLS/FID)」「優先度(大/中/小)」にわけて、サービス側に施策を提案します。具体的な改善方法に加え「優先度」をつけることがポイントで、多忙なサービス側の実装コストを最小限にすることが狙いです。
以下は、実際に各サービスに分析内容をまとめて提示した画面です。
サービスの担当者の方には、修正方針を見ていただいた上で、どのように対応するかと、その進捗を随時記載をしてもらうようにしています。
4. 全社共通プラットフォームへの改善依頼
ヤフーでは開発を効率化するため、全社共通で使用できるライブラリやプラットフォームが提供されています。今回の調査から改善を効率的に実施するためには、共通部分にも対策を入れる必要なことがわかってきました。
プロジェクトでは、「広告システム」「共通ヘッダーシステム」「共通JSライブラリ」3つの箇所の課題にまずは絞って対策の実施を依頼しました。さまざまな対策を実施しましたが、主な修正は以下のようなものです。
Cache-Controlヘッダの最適化
共通で使用されるスクリプトやCSSはCDNから配信されていますが、適切にバージョン管理されていれば、キャッシュ時間を延長することが可能です。全社で提供されるリソースの多くは、一律初期値のまま短いキャッシュ時間が設定されていましたが、可能な範囲で最長になるように調整しました。
// 最長のキャッシュ時間を設定する例
Cache-Control: public, max-age=31536000, immutable
document.writeの撲滅
古くから利用されていた広告スクリプトの中には、document.write
の処理を使用して記述されている箇所がありました。
document.writeはMDN Web Docsの説明にもあるように、レンダリングを途中でブロックし、ブラウザの最適化を阻害します。今回は広告の影響がないことを調査した上で、非同期でDOMを挿入する処理に変更しました。
広告で発生していたレイアウトシフトを軽減
広告は掲載される箇所や、引き当てた状況によって掲載内容が変化します。そのため、掲載する広告によっては画面が大きくガタついて表示されてしまうことがありました。広告内容が不定の場合、以下のような対策をとってレイアウトシフトを軽減できるように、導入方法を見直すようにしました。
- 複数種類の広告が配信される場合、その中で最小の広告サイズで
min-height
、min-width
を設定することでガタつきを最小限に抑える - 広告が引き当たらなかったとき、サービス側で代替画像(フィラー)を掲載できるようにする
共通ヘッダーで発生していたレイアウトシフトを軽減
全社共通で使用されているヘッダー部分でも、下図のキャンペーン枠でガタツキを発生していました。キャンペーン枠は、クライアントサイドでデータ取得が行われ、ユーザーや時期によって引き当たる内容が変化します。もちろん、コンテンツが引き当たらない場合も存在します。
調査を進めたところ、キャンペーン枠が引き当たる場合と引き当たらない場合を比較したとき、前者の割合のほうが確率的に高い状態であることがわかりました。そこで、ガタツキを軽減するため以下のような対策を実施しています。
- 「キャンペーンがあれば枠を挿入」という処理から、「キャンペーンがなければ枠を削除」という処理に変更できるようにする
- 広告枠を運用している部門と連携し、なるべく訴求枠が引き当たるように運用を調整
こうした施策によって、多くのサービスでのCLSを低下させることができています。
成果について
上記の施策を各サービスで実施した結果、Yahoo! JAPAN トップページ、Yahoo!知恵袋、Yahoo!ニュース、Yahoo! BEAUTY、Yahoo!不動産、Yahoo!映画、カービュー、LOHACOなど、多くのサービスで良い結果が出始めています。
注力サービスに絞てベンチマークとの勝率を比較を可視化したところ、開始当初は62.1%の勝率でしたが、現在は75.9%まで向上しました。
実施前 | 実施後 |
---|---|
62.1% | 75.9% |
Yahoo!映画やYahoo!知恵袋は改善を実施した代表的なサービスですが、実施後に”改善が必要”の領域が減少していることがわかります。
各サービスの改善内容については、また後日別の記事でも詳細にお伝えしたいと思います。
コスパの良い施策の紹介
さて、ここまで多くのサービスの分析していると、比較的簡単に実施でき、かつスコアが上がりやすい施策がわかってきます。ここでは、ヤフー全社で案内した高優先度の施策の一部をご紹介します。
一般的な項目も多いですが、これらがまだ実施できていない場合、すぐに改善できる余地があるということでもあります。以下のチェックポイントを、今一度確認してみてください。
- 重要なリソースの読み込み順を整える
- 圧縮配信する
- 初期画面のレイアウトシフトの抑制する
- メインスレッドを阻害しているスクリプトを遅延読み込みする
- img/iframeの遅延読み込みを実施する
施策1. 重要なリソースの読み込み順を整える
Resource Hintsを使用すると、リソースの優先度をブラウザに認識させ、読み込み順序を最適化できます。初期画面に必要なリソースの読み込みが後方にある場合、その分LCPやFIDが遅延することになります。
<link rel="preload" href="[リソースのパス]">
特に忘れがちなのが、JS/CSSは指定しているが、メイン画像(LCP要素)の先読みを実施していないケースです。LCPに当たるメイン画像要素はResource Hintsでpreloadの指定に含めるようにしましょう。
Draft段階ではありますが、Priority Hintsという標準も準備が進んでいます。こちらは現在Google Chrome 101でのみ使用でき、より最適化された読み込み優先度を定義できます。
この改善は単純なタグの挿入や、属性の指定で効果を得られますので、まだ実施されていない場合は改めて見直してみてください。
施策2. 圧縮配信する
gzipはコンテンツやリソースの圧縮配信によく使用される技術です。現在は、ブラウザにもWebサーバーにも標準として実装されていますが、ヤフー社内の実例では万一設定を行わなかった場合SpeedIndex
のスコアに大きく影響することがわかっています。配信しているシステムで、gzip配信が有効になっているか今一度確認してください。
施策3. 初期画面のレイアウトシフトの抑制
パフォーマンス改善の記事ではよく話に上がっている、”ガタツキ”の改善です。
こうしたガタツキを可視化するスコアとして、CLS(Cumulative Layout Shift)という値が定義されています。CLSのメトリクスや改善方法の詳細は、以下の記事をぜひ参考にしてみてください。
- https://web.dev/optimize-cls/ (外部サイト)
CLS対応が考慮されていないページの場合、すべての箇所で改善を実施するのは骨がおれる作業です。少ない修正箇所で大きな効果を得るためには、多くのユーザーが閲覧するページの初期画面のみ優先して実施することが有効です。作業ボリュームが大きくて未実施の場合は、場所を絞って実施できないかを確認してみてください。
施策4. メインスレッドを阻害しているスクリプトを遅延読み込みする
レンダリングのためのメインスレッドを阻害すると、FIDのスコアに影響します。Lighthouseで算出されるパフォーマンス全体のスコアは、各指標にウェイトを加味した合計スコアとなっていますが、中でもTBTは最も高い30%のウェイトを占めていることから、非常に重要な改善要素となっています.
これらは、本質的に改善を実施する場合は、アプリケーションの処理自体を見直さねばなりませんが、多くの場合読み込みのタイミングをわけるだけで大きく改善できます。以下のポイントを確認してみると良いでしょう。
- async/defer を付けて外部スクリプトを読み込んでいるか確認する
外部スクリプトを読み込むときは、基本的に遅延読み込みのための属性をつけることを推奨します。これらの属性を付けない場合、レンダリングを途中で止めてしまうために大幅に処理が遅延します。
以下のBookmarkletは、属性値のついていないscriptをConsole画面出力できるので、調査に活用してみてください。
async/defer属性がついていないscriptタグを出力するBookmarklet
javascript:(function()%7Bconsole.log(Array.from(document.getElementsByTagName('script')).filter((e)%20%3D%3E%20!e.defer%20%26%26%20!e.async).map((e)%20%3D%3E%20e.attributes.getNamedItem('src')%3F.value).filter((e)%3D%3E%20e))%7D)()%3B
- スクリプト自体をわけて、遅延読み込みできないか検討する
スクリプトの役割によって、後方に読み込み自体をずらせる場合があります。メインの処理に関連が薄いサードパーティの処理は、以下の(3)や(4)に移動できるはずです。
タイミング | 例 | 実装方法 |
---|---|---|
(1) すぐに実行 | DOMなどに関連せず独立して実行できる重要処理など | async属性をつけてscriptを記述 (<script async src="****"> ) |
(2) ドキュメントを読み込み後、順次実行 | アプリケーションのメイン処理など | defer属性を付けてscriptを記述 (<script defer src="****"> ) |
(3) ページが操作可能になった後、空き時間に読み込んで実行 | アナリティクス、広告のリターゲティング、タグマネージャなど | DOMContentLoadedのイベントを待って、RequestIdleCallbackを用いてスクリプトを読み込んで実行 (なおReactの場合は、useEffect内でRequestIdleCallbackを用いてスクリプトを読み込むのが良いでしょう) |
(4) ページ全体の読み込み完了後、空き時間に読み込んで実行 | SNSウィジェットなど、コンテンツに無関係なサブ要素 | windowのloaded のイベントを待って、RequestIdleCallbackを用いてスクリプトを実行 |
例えば、(4)であれば、以下のような処理になります。読み込むとページの読み込み完了後に外部スクリプト自体が読み込まれるので、メイン処理に無関係な処理をまるごと遅延実行できます。
const loadScript = (src: string, onLoad: () => void) => {
const script = document.createElement('script');
script.setAttribute('async', '');
script.onload = onLoad;
script.src = src;
document.body.appendChild(script);
};
const loadLazyScript = (src: string, onLoad: () => void) => {
if (document.readyState === 'complete') {
requestIdleCallback(() => loadScript(src));
} else {
window.addEventListener('load', () => {
requestIdleCallback(() => loadScript(src));
});
}
}
loadLazyScript('[スクリプトのURL]', () => { /* スクリプト読み込み後の処理 */ });
メインの処理の中に今すぐに実行しなくても良い処理がないか確認し、後方のタイミングに移動できないか検討してみてください。
施策5. img/iframeの遅延読み込みを実施する
Internet Explorer 11がEOLとなった今、ほとんどのモダンブラウザで遅延読み込みは要素に属性指定するだけで実現できます。LCPにあたるメイン要素を除き、imgタグやiframeタグには、loading="lazy"
の指定を忘れずに入れておくと良いと思います。
<img src="image.jpg" alt="..." loading="lazy">
<iframe src="player.html" title="..." loading="lazy"></iframe>
今後の展開
当初はモバイルWebを優先して実施してきたため、現在はPCサイトの改善も視野に入れて引き続き改善を進めています。また、プロジェクトでは、引き続き以下の改善ができないかを検討しています。
ビジネス指標と紐付けたスコアを定常的に取得できるようにする
改善の影響を明確に取得するためには、条件を整え専用のA/Bテストを実施するなど、個別に設定することが不可欠でした。時系列的な比較では、コンテンツや季節的な要因などが影響し、比較にならないことも多々ありました。
離脱率などの指標はパフォーマンスのスコアとの相関が高い傾向があることから、全社共通で導入されている社内システムと連動して実施できないか検討していきたいと思います。
INPの改善施策を実施する
2022年5月頃、GoogleからINP(Interaction to Next Paint)
という新しいCore Web Vitalsの指標が提示されました。INPは、ユーザーの入力に対してアプリが適切に応答を返すまでの時間を表す指標です。ユーザーがアプリケーションを操作してから、200ms以内の反応を求められます。
UIによる改善を含め多くの改善手法がありますが、スクリプトがメインスレッドを阻害しないようにする改善は多くのサービスで有効といえます。例えば、React18では、Suspence
、useTransition
、useDeferredValue
など、重たい処理をメインスレッドから切り離して並列処理するための仕組みが導入されています。こうした知見を社内で先行して取り入れ、横展開を進めていきたいと考えています。
おわりに
私自身、全社で長期にわたる取り組みは初めてでしたが、サービスが滞りなく改善できる「仕組みづくり」の重要性を改めて実感しています。サービスによっては、ビジネス案件との優先度のかねあいで実施できる時間が限られていたり、より密に技術協力を必要とするサービスもありました。どのように案内すれば各サービス最小限のコストで施策を実施できるのか、また実施してくれるのか、試行錯誤をする日々でした。
今回の取り組みは各サービスに修正依頼する形で実施してきましたが、プロジェクトの最終ゴールは、各組織で自立的に改善が進む文化を形成することです。プロジェクトトップページの下部には、当時社長であった川邊さんからいただいたメッセージが戒めのように掲載されています。体現できるよう、引き続き活動を続けたいと思います。
最後に、今回の改善に関わっていただいた、チームメンバーの方、サービスの方、プラットフォームの担当の方、多くの方の協力で改善を実施できています。本当にありがとうございます。この場を借りて、お礼を申し上げます。
ヤフーでは引き続き、快適な操作性をスタンダードとして提供できるサービス・組織を目指し、邁進していきたいと思います。
少し速くなったYahoo! JAPANを、引き続きよろしくお願いいたします。
こちらの記事のご感想を聞かせください。
- 学びがある
- わかりやすい
- 新しい視点
ご感想ありがとうございました