ユーザー体験を表す重要な指標「Core Web Vitals」の改善

takuhyaku氏:「タウンワークにおけるCore Web Vitals改善施策とそれにより見えてきたもの」と題してtakuhyakuが発表します。

私は株式会社リクルートで、主にHR領域のエンジニアリングをやっています。今は、タウンワークのWebのエンジニアとして主にバックエンドも触っていて、フロントエンドも時々やっている状態です。

2016年の新卒で、これまではHR領域サービスのネイティブアプリ開発、タウンワークの入稿された原稿のバッチシステムの改善、WebのエンハンスチームによるWeb開発、あとはベトナムで、オフショア開発の検証をやっていました。現在は、遊撃部隊としてCore Web Vitalsの改善を主導しています。

今回は、Core Web Vitalsの話をしますが、一応おさらいすると、LCP(Largest Contentful Paint)とFID(First Input Delay)とCLS(Cumulative Layout Shift)が存在していて、大雑把に言うと、ユーザー体験を表す重要な指標として、この3つが定義されています。これは、「主要コンテンツが速く表示されて」「すぐにページが操作できて」「予期せぬカクツキがない」ページが至高だよね、という指標です。この3つの指標を追い求めましょう、とGoogleから出されています。

本日は、タウンワークにおいて、Core Web Vitalsの改善をしていくうえで、これまでにやったWebパフォーマンスの改善と知見の共有について話したいと思います。

背景ですが、Core Web Vitalsを2021年5月からランキングシステムに組み込むという発表が、2020年11月にありました。最新発表である6月中旬から段階的に取り入れるようになっているのですが、改善期間である3月と4月で、まずはできるところから改善しようということで始めました。

(タウンワークについて)ご存じの方もいるかもしれませんが、どういうサービスかというと、アルバイトを探しているカスタマーと、働き手を探しているクライアントを結びつける、アルバイト・社員求人のサイトです。

「画像データ」「フィールドデータ」で現状を確認

改善スコープは、上記期間で改善リリースまで行える、ROI(投資利益率)が高いと見込んだ施策を取っています。SEOチームとも話して、より優先度が高い改善対象の画面を4つピックアップして、改善を行いました。

最初に関東や関西などといったエリアを選ぶ「地方選択画面」と、そこから都道府県を選んだあとに飛ぶ「エリアトップ画面」、そしてそこに検索条件を入れた時に出てくる「求人一覧画面」、あとはそこから詳細の情報を見る「求人詳細画面」。主にこの4つの画面に対して、改善していこうとなりました。

まずは、現状のパフォーマンスを計測したので、計測のデータ種類を2つ紹介します。

1つ目が「画像データ」と言われるもの。これは一連の固定されたネットワーク条件で、1台の端末がページを読み込むことをシミュレートしたデータです。シミュレートした環境でのデータなので、パフォーマンス問題のデバッグなどに役立ちます。Chromeの拡張に「Lighthouse」というツールがありますが、そのLighthouseはモバイルネットワーク上のミッドレンジ端末で、ページ読み込みをシミュレートした結果を出します。

2つ目が「フィールドデータ」と言われるもの。こちらは、実際のさまざまな端末やネットワークの条件において、実ユーザーから匿名で送られてきたパフォーマンスデータで、過去28日間の収集期間をもとに算出しています。

主にランキングシステムに組み込まれるデータはこのフィールドデータになるので、まずはこれをちょっと見てみるのが、現状サイトがどうなっているかを見るためにもいいと思います。

実際に、先ほどピックした4画面において、現状のフィールドデータを見てみます。

4画面あって、Core Web Vitalsの指標にはFCPは入っていないのですが、それ以外の3つの指標、FIDとLCPとCLSは入っているので、併せて表示します。

左上のFCP以外は、改善前もオールグリーンではあったものの、FCPは若干赤くなっていました。そのFCPと、LCPと言われる「ネットワークI/O」にかかわるオーバーヘッドで、ちょっとかかっていそう、というところがありました。ここのFCPとLCPに関して、改善の余地がありそうなので、実際に計測をしてボトルネックを見にいきました。

ボトルネックを計測して確認

ネットワークI/Oにどういうものがあるのかをパッと洗い出してみると、「Web Font」「3rd Party Resources」「Unused JS/CSS」、画像系、サーバーのレスポンス、キャッシュ制御など、いろいろあると思います。

Web Fontは、タウンワークでは未使用だったので、そこまでリソースとして「けっこう取っているよね」みたいなところはなかったです。

「3rd Party Resources」は、「WebPageTest」という計測ツールがあって、そこから「Request Map」を使用して見てみました。左がtownwork.netからリクエストしているRequest Mapで、右側がリクルートの別サービスのRequest Mapですが、これを見た感じだと、タウンワークは3rd Partyタグをいろいろと入れていないので、ゴチャゴチャしている状態ではありませんでした。3rd Party Resourcesに関しても、そこまで改善することでもないのかなと思ったので、施策対象から外しています。

Unused JS/CSSと画像とその他については、実際に「Chrome DevTools」を使用して計測していました。Unused JS/CSSに関しては、全画面共通で呼ばれているCSSやJSもあります。これはエリアトップ画面だけではあるのですが、Unusedな割合が高いものが何ファイルかありました。

ただ、今回のCore Web Vitalsの施策とは別に、フロントエンドの開発改善を並行してやっていました。それまでは、先ほど言ったように、いろいろな画面から呼ばれる共通JSや共通CSSが存在していて、その依存関係が複雑でエンハンス・保守しにくい状態になっていました。

それを2〜3年ほど前に、webpackなどを導入して、画面ごとにJS、CSSをしっかりと呼ぶように設計しようとなりました。一気にはできないので、徐々に改善する取り組みをやっていて、これも今回のスコープにおける改善施策対象としては、外しています。

画像系の改善施策

画像系の部分も、Chrome DevToolsのパフォーマンスにおけるネットワークとCPUのところをいじって、見てみました。自分の端末や会社から支給されているハイスペックな開発端末だと性能がよくて、ボトルネックが見えづらい可能性があったからです。私の場合はネットワークをFast3Gにして、CPUを4倍低速にしてから、計測を行いました。

一覧画面に関して、ここを見てもらうとわかるのですが、この画像が最初に読み込まれています。これは一番下のほうにスクロールしないと出てこない画像で、要するにFirst Viewに関係ない画像を最初のロード時に呼びにいっていました。こういう読み込みは最初に必要なさそうだよねと、「`loading=lazy`」を付与するなどして遅延読み込みさせることで、改善できるんじゃないかというところが1つありました。

今のは読み込みの「不要か必要か」みたいな話なのですが、とは言え、画像があることが仕様としても必要なので、画像のサイズを小さくしたら、読み込み速度が改善するのではないかと思いました。そもそも現状、その画像のサイズの最適化ができているのかを、開発フローから見直したところ、開発フローでは、どこで画像サイズを最適化するかが決まっていませんでした。

開発フローを簡単に書いたのですが、案件起案されて、そのあとにデザイナーさんがデザインを作成してくれて、それをもとにフロントエンドが資材を使って実装して、デザインレビューをするかたちでした。

デザイナーさんがデザイン作成時に作ってくれることもあれば、フロントエンドの実装で、エンジニアが画像サイズを小さくして実装することも、起こっていました。ここを再度見直して、サイズ最適化できそうなところや漏れがありそうなところを、改善施策として追加しています。なので、画像に関しては、遅延読み込みとサイズの最適化を行っています。

その他の施策

そのほかにもいろいろ見にいったのですが、例えばキャッシュバスティングのクエリ、この「v=」が付いているものに関してもmax-ageが0で、キャッシュが効いていませんでした。なんのためのバージョンなのか、あるいはキャッシュバスティング用のクエリなのか。「なんでこうなっているんや?」みたいなところの原因はわからなかったのですが、そのような状態だったので、同じバージョンのものでも、毎回サーバーリクエストが発生してしまっていて、キャッシュ制御の対応ができるんじゃないかというところがありました。

また、index.htmlを見にいくと、現状、タウンワークはWebサーバーはApacheなのですが、Apacheのmod_deflateモジュールによってgzip圧縮を行っていて、こちらをより圧縮率が高いBrotliの圧縮にすることによって、転送量の削減が見込まれるのではないかと、施策として考えていました。

3つ目はCSSの例ですが、後続処理をブロックして、かつ全画面で呼ばれているCSSの読み込みがありました。具体的には、「@import」しているCSSがあって、ここは直列処理になっていそうなので、@importの削除ができるんじゃないかというところが見えてきました。

ということで、まとめると、その他のところではキャッシュ制御の対応、Brotli圧縮、あとCSSの@import削除ができそうというところを施策として置きました。

それぞれのビフォー・アフター

今回は1ヶ月ちょっとだったのですが、実施できる小さい粒の施策かつ、LCPとFCPに関連して改善できそうな施策として、こちらをやっていきました。

施策の実施を行って、画像は遅延読み込みを行って、どういう対象ファイルがあるかを洗い出して、「loading="lazy"」を追加していきました。先ほどお見せしましたが、最初のロード時のFirst Viewに関係ないところは、消えていることが確認できると思います。

サイズの最適化についても、ビフォーとアフターがあります。すでに最適化されている画像もいっぱいあったのですが、洗い出した中でできそうなものはこれだけ改善しました。だいたい20パーセントから40パーセントぐらい、画像サイズの削減ができたかなと思います。

キャッシュバスティング用のクエリが付いている資材に対しては、「max-age=0」だったものを1年に変更しました。Apacheのconfファイルを修正して、CSSとJSに対してバージョンクエリが付いているものに対して、max-ageを1年に設定しています。それによって、毎回サーバーリクエストが走っていたところが、キャッシュを使用するようになり、しっかりとキャッシュの制御ができていることが確認できました。

Brotli圧縮に関しては、標準モジュールとして使用できるわけではなく、Apacheの再コンパイルが必要になります。インフラチームと話したところ、サービスへの影響が大きそうとなったので、今回のスコープからは除外しました。CSSで@importも直列をなくして、ここのブロックがなくなりました。

実施後に、フィールドデータとNavigation Timing APIで、実測値を取りました。(スライドを示し)LCPもFCPも0.4秒速くなっていて、FCPも含めてすべてオールグリーンという状態になっています。求人詳細の画面はそこまで速くなっていないのですが、グリーンの割合が増えているため、改善幅はあるのかなと思います。

(計測には)フィールドデータだけではなくて、Navigation Timing APIというほぼすべてのブラウザで使用可能なAPIも使用しました。これによって、実測値が取れます。雑な実装にはなりますが、あるJSの途中までの時間をログ出力して、それをBigQueryに送っているかたちです。

実際に、施策をリリースしたタイミングでページの読み込み速度が下がっているところが見てとれます。

最後に、サマリーです。対象画面の4分の3がFCPとLCPで20〜30パーセントほど速度改善ができました。実測値を見ても、しっかりとページ速度の向上に寄与していることがわかりました。

改善によって見えてきたことを、いろいろなステークホルダーの方と話したのですが、この改善活動を進めたことで、大きな粒度の改善ではなくて、小さな粒度で改善活動できました。

「Akamai」のCDNをいろいろと導入したり、2年前にフロントエンドの改善をしたりしたことによって、大きい粒度ではなくて、小さい粒度で改善を行うことができました。そのため、日々のパフォーマンス改善は大事だと感じました。

事業KPIにパフォーマンスがどう影響しているかは、まだ計測できていなくて、ここが計測できれば、いろいろな方に改善活動は意味あると言えるようになると思うので、そこはしっかりと計測していきたいと思っています。