Core Web Vitalsを改善してスコアを13点から68点に上げた話(進め方・やったこと・効果)

:::note info
この記事はLITALICO Advent Calendar 2022のカレンダー1の13日目の記事です。
https://qiita.com/advent-calendar/2022/litalico
:::


# 自己紹介

株式会社LITALICOでWEBエンジニアをやっています。ti-aiutoと申します。

普段は「LITALICO発達ナビ」という、神経発達症(発達障害)や知的発達症(知的障害)などのある子ども・その保護者の方をサポートするWEBサービスの開発を担当しているのと、最近はデータエンジニア見習いのような仕事もしています。

今回のアドベントカレンダーでは下記の三部作も書いているのでよかったらご覧ください。

https://qiita.com/ti_aiuto/items/638595a148e7d16dd70f

# 背景

サイトの中で一番アクセスが多いのは専門家の監修による数多くのコラム記事が無料で読める機能なのですが、このページへの流入元は検索エンジンもかなり多いです。
このコラム記事からの会員登録も多いので、コラム記事への流入を増やすことは事業上も重要な課題となっています。

そんな中、Googleのアルゴリズム変更により、WEBページのパフォーマンス(Core Web Vitals)が検索順位に影響するという話が出てきたため、このスコアを改善していこうという話になりました。

今回の記事では、そのコラムページのCore Web Vitalsを改善していく中で、どう進めたのか・何をやったのか・どんな結果になったのかをまとめていきたいと思います。


:::note warn
実施したのは一年半前の話なのでスクリーンショットの表示や記載が古いですが、基本的にやったほうがいいことは変わっていないはずなので今でも役に立つと思います。

ちなみになぜ今なのかというと、去年はとても忙しくてアドベントカレンダー記事をじっくり書く時間がなかったのと、ここ1年半で他事業部から何回か問い合わせがあって丁寧にまとめたほうが良さそうと思ったからです。
:::

https://digitalidentity.co.jp/blog/seo/algorithm/core-web-vitals.html

https://developers.google.com/search/blog/2021/04/more-details-page-experience


# 進め方

## 目標

具体的にここまで改善したい！というラインがあったわけではないのですが（何点とればいいのかもわからないし）、まずはPage Speed Insightsの点数の赤エリアを脱する、あわよくば緑エリアに入る、くらいで考えていました。


実施内容については、Page Speed Insightsで提示された改善案や検索して出てきた他社事例を参考に、実装コストを勘案しつつ、運用上・事業上の事情も踏まえて、できそうなものから実施していきました。

Page Speed Insightsとはなんぞやという方は下記などをご参照ください。

https://netshop.impress.co.jp/node/10014

なお、各事例に掲載している表のスコアについては、最初の一回を除き、連続した3回分のスコアを計測しています。

## Page Speed Insightsの改善案の見方
「改善できる項目」のところと「診断」のところにこんなことやるといいですよというのが表示されています。▼を開くとより詳細な情報が見れます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31474/a66262c9-acaf-c67a-9a2d-ba54ddaf9b10.png)

## 開発者ツールのPerfomanceタブの活用

細かいチューニングはChromeの開発者ツールのPerformanceタブを活用して行いました。

具体的に何のダウンロードや実行が原因でどれくらい処理が後ろにずれ込んでいるかが分析できます。
諸々のタイミングを見るときは、低速回線を再現する機能を使うとよりわかりやすくなります。

細かい使い方は下記のような記事が役に立つと思います。

https://engineering.mercari.com/blog/entry/2018-12-12-090156/

https://zenn.dev/koki_tech/articles/9deb70d0a9befb

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31474/ebd5e2a4-29d5-f903-9e10-318b1c50f61d.png)


（Performance Insightsタブにリニューアルされたと思ってそっちの画像を貼っていましたが、パフォーマンスタブもちゃんと残っていました

## 点数の試算

どの点数をどれくらいあげるとスコアがどう変わるかはツールで試算できます。
配点が大きいのは25%になっているLargest Contentful PaintとTotal Blocking Timeということも分かります。

https://googlechrome.github.io/lighthouse/scorecalc/#FCP=8000&SI=11900&LCP=1080&TTI=17000&TBT=2000&CLS=0.82&FMP=0&FCI=0&device=mobile&version=6

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31474/d0969e27-cfa4-8203-ad5b-3ec79da8ceb7.png)


## 表記について

Core Web Vitalsの各指標について、冗長に感じたところは頭字語で略称にしているのでご注意ください。
例：Largest Contentful Paint → LCP

# やったこと

## 実施前

### 着手時点でやってあったこと

着手時点で次のような部分は対応済みでした。

* サーバサイドのレスポンス時間の改善
    * テーブルのインデックス付与などDBレベルのパフォーマンスチューニング
    * viewの中にキャッシュを入れる
* 大部分の画像にlazyloadを設定
    * 記事内の画像などスクロールしてから表示される画像
    *  (`lazysizes.js` というライブラリを活用)

特にサーバのレスポンス時間が遅いと全てが後ろ倒しになってスコアが下がるので、未対応の場合は要注意です。

### 着手時点での点数

最初の時点ではともかく全体的に数値が悪いです。３回計測しても3回とも安定して（？）悪い数値になっています。
以後ではこの数値とも比較しながら改善効果を見ていきます。


| |スコア|FCP|SI|LCP|TTI|TBT|CLS|
|:----|:----|:----|:----|:----|:----|:----|:----|
|1回目|20|4|9.8|9.2|15.2|960|0.107|
|2回目|17|4.4|10.4|5.9|15.7|1980|0|
|3回目|13|4.5|11.1|7.8|16|2170|0.104|
|平均|16.67|4.30|10.43|7.63|15.63|1703.33|0.07|

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31474/0daad5ad-0434-90ee-54f5-0a8098eea46f.png)


## JSファイル・CSSファイルをコラム機能専用に切り出す

### 概要

このポータルサイトというのは6年間の機能追加が積み重なっていることもあり、サービス自体もソースコードも巨大になっています。
具体的には、コミュニティ(SNS)機能・質問投稿機能・アンケート機能・福祉施設検索などいろんな機能のソースコードが一緒に入っている状態で、使われているライブラリも色々でした。

以前から別の文脈で、機能ごとにJSファイルを分割して開発時の影響範囲を切り分けられるようにしよう、というリファクタリングをやっていたので、そのリファクタも兼ねてJSのファイルを分割することにしました。

https://blog.ta.kasaki.info/archives/6

### 結果

コラム記事用にJSファイルを切り出したことで、ファイルサイズが319KB→98KBに軽量化されました。
スコアは10点近く改善しています。

| |スコア|FCP|SI|LCP|TTI|TBT|CLS|
|:----|:----|:----|:----|:----|:----|:----|:----|
|1回目|30|2.6|7.6|5.2|14.3|1910|0|
|2回目|37|2.6|7.3|4|14.2|1860|0|
|3回目|27|2.7|7.8|6.9|12.9|1220|0.104|
|平均|31.33|2.63|7.57|5.37|13.80|1663.33|0.03|
|直前との比較| <font color="blue">188.00%</font> | <font color="blue">61.24%</font> | <font color="blue">72.52%</font> | <font color="blue">70.31%</font> | <font color="blue">88.27%</font> | <font color="blue">97.65%</font> | 49.29% |

## JSファイルの読み込みを遅延させる（deferをつける）

### 概要

JSファイルが読み込まれないと描画できないものもあるのでそれも大事なのですが、一方で大部分はSSRなのでHTMLの中身をどんどんparseしてリソースも読み込んでいってくれないとページが描画できません。特にLargest Contentの画像の読み込みが遅れてしまうのは痛いです。

JSファイルの読み込み中に他の処理がブロックされてしまう問題については、 `defer` 属性をつけることで改善できるということなので、つけてみました。

https://developer.mozilla.org/ja/docs/Web/HTML/Element/script

### 結果

First Contenful Paintが1秒ほど改善しました。
この1秒がJSの読み込みによりブロックされていた時間ということになりますかね。

ただ、配点の大きいLCPが悪化してしまいました。

| |スコア|FCP|SI|LCP|TTI|TBT|CLS|
|:----|:----|:----|:----|:----|:----|:----|:----|
|1回目|24|1.9|6.9|10.7|12.6|1730|0.299|
|2回目|25|1.9|6.4|10.3|13|1820|0.299|
|3回目|27|1.9|6.8|10.9|12.5|1170|0.299|
|平均|25.333|1.900|6.700|10.633|12.700|1573.333|0.299|
|直前との比較| <font color="red">80.85%</font> | <font color="blue">72.15%</font> | <font color="blue">88.55%</font> | <font color="red">198.14%</font> | <font color="blue">92.03%</font> | <font color="blue">94.59%</font> | 862.50% |
|最初との比較|152.00%|44.19%|64.22%|139.30%|81.24%|92.37%|425.12%|

## 画像が読み込まれたときにｶﾞｸｯってなるのをサイズ指定で解決

### 概要

Cumulative Layout Shiftは配点は高くないですが、UX上違和感を減らすことも重要なので先に着手することにしました。

今回の場合だと横長のバナー画像が読み込まれたときに内容がｶﾞｸｯっと下に下がってしまうのを改善すれば良いのですが、これについては `image` タグに `width` と `height` を指定すればOKです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31474/738e0c0d-efb4-00d9-5ca4-931083cb1f2b.png)


### 結果

これによりCLSが大幅改善、他は変わらずです。

| |スコア|FCP|SI|LCP|TTI|TBT|CLS|
|:----|:----|:----|:----|:----|:----|:----|:----|
|1回目|29|1.9|6.8|11.1|12.5|1330|0.001|
|2回目|30|1.7|6.8|10|12.3|1170|0.001|
|3回目|27|1.7|7|10.8|12.7|1860|0.001|
|平均|28.667|1.767|6.867|10.633|12.500|1453.333|0.001|
|直前との比較|113.16%|92.98%|102.49%|100.00%|98.43%|92.37%| <font color="blue">0.33% </font>|
|最初との比較|172.00%|41.09%|65.81%|139.30%|79.96%|85.32%| <font color="blue">1.42% </font> |


## Largest Contentを最速で読み込む（preloadを指定する）

### 概要

Largest Contentful Paintは配点が最大の25%になっています。
コラム記事ページの場合はどーんと画像が表示される画像がLargest Contentとなっていたので（これはPerformance Insightsで調べられます）、この画像の読み込みを早めることにしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31474/3e3211ce-bad6-89dd-dd5a-58015c711341.png)

linkタグにpreloadを指定することで早く読み込んでほしいリソースを優先的に読み込んでくれる機能があるので、これを使います。

https://developer.mozilla.org/ja/docs/Web/HTML/Link_types/preload

Performanceタブで見ると画像の読み込み（横長の赤四角）にLCP（縦長の赤四角）がつられていることが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31474/1da1d94b-04dc-742b-2244-35956339ffd7.png)

### 結果

期待通りにLCPが早くなりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31474/284d03d5-31f0-0b60-2f21-d663dcf2c9a5.png)

LCPが大幅改善し、スコアが10点近く改善しています。

| |スコア|FCP|SI|LCP|TTI|TBT|CLS|
|:----|:----|:----|:----|:----|:----|:----|:----|
|1回目|44|1.9|6.5|3.8|12.8|1180|0|
|2回目|40|1.9|7|4|13.3|1370|0.001|
|3回目|50|1.9|6.8|3.5|13.7|840|0.001|
|平均|44.667|1.900|6.767|3.767|13.267|1130.000|0.001|
|直前との比較| <font color="blue">155.81%</font> |107.55%|98.54%| <font color="blue">35.42%</font> |106.13%| <font color="blue">77.75%</font> |66.67%|
|最初との比較|268.00%|44.19%|64.86%|49.34%|84.86%|66.34%|0.95%|

## 優先度の低い画像やCSSをlazyloadにして広告に帯域を譲る

### 概要

細かく分析していくと、様々な画像やCSSファイルを読み込んでいることで、次のような悪影響が生じている可能性が出てきました。

* Total Blocking Timeが長い
    * 画像を先に読み込んでいるため広告など重いJSの読み込みが後ろ倒しになる
* DocumentContentLoadedの実行タイミングが遅いので、JSの実行やそれによる描画が遅れる
    * JSファイルを読み込まないとDOMContentLoadedが発火しない(defer属性の挙動)が、他のリソースが先に読み込まれてしまっている

対応としては、読み込みが遅れてもUX上もパフォーマンス上もあまり困らない画像は、lazyloadに変更することで、DocumentContentLoadedイベントが早く発火するようにします。
※使用している `lazysizes.js` の設定も変えて、DocumentContentLoadedが発火してからlazyloadの画像を読み込むようにしました

これにより空いた帯域で広告のJSなど他のリソースの読み込みが早まります。

優先度が低い（読み込みが多少遅れてもUX上もパフォーマンス上もあまり困らない）画像の一例
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31474/9e64f33d-0159-d225-351d-39064bb96e13.png)

赤矢印のリソースを後ろにずらしたい
青矢印のリソースを先に読み込みたい
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31474/df7b2719-4d20-9275-1c44-d33f315006b5.png)

### 結果

この変更によりLCPが改善しました。

| |スコア|FCP|SI|LCP|TTI|TBT|CLS|
|:----|:----|:----|:----|:----|:----|:----|:----|
|1回目|39|1.9|7.3|4|12.9|1680|0.001|
|2回目|58|1.9|6.6|2.1|11.9|900|0.001|
|3回目|51|1.9|6.4|2.6|12.7|1530|0|
|平均|49.33|1.90|6.77|2.90|12.50|1370.00|0.00|
|直前との比較| <font color="blue">110.45%</font> |100.00%|100.00%| <font color="blue">76.99%</font> |94.22%|121.24%|100.00%|
|最初との比較|296.00%|44.19%|64.86%|37.99%|79.96%|80.43%|0.95%|

変更後
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31474/d21dd63f-1f77-43bb-a502-3169a1a7b333.png)

## PageSpeed InsightsがHTTP/2をサポート

### 概要

社内のSlackでRSS通知を設定している有名なブログで仕様変更のお知らせがあったので、このタイミングでも計測してみました。

https://www.suzukikenichi.com/blog/pagespeed-insights-now-supports-http2/

> サーバーが HTTP/2 をサポートしていれば、PageSpeed Insights は HTTP/2 を使います。
> これにより、同じ URL であっても HTTP/1.1 を使っていた以前のバージョンよりもスコアが良くなっているかもしれません。

### 結果

確かに若干スコアが良くなっていました

| |スコア|FCP|SI|LCP|TTI|TBT|CLS|
|:----|:----|:----|:----|:----|:----|:----|:----|
|1回目|58|1.5|5.4|1.6|12.8|1230|0.022|
|2回目|57|1.5|4.8|2|12.7|1700|0.001|
|3回目|63|1.5|4.7|1.5|11.7|990|0.001|
|平均|59.333|1.500|4.967|1.700|12.400|1306.667|0.008|
|直前との比較| <font color="blue">120.27%</font> | <font color="blue">78.95%</font> | <font color="blue">73.40%</font> | <font color="blue">58.62%</font> |99.20%|95.38%|1200.00%|
|最初との比較|356.00%|34.88%|47.60%|22.27%|79.32%|76.71%|11.37%|

## Googleアドマネージャー広告を最速で読み込む（preloadを指定する）

### 概要

画像にlazyloadを付けて帯域を空けたときの話と考え方は同様です。
Googleアドマネージャー広告の実行時間が長いので、JSの実行時間全体が長引いてしまうと考え、そのJSファイルを最優先で読み込むようにしてみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31474/0943cc65-0e4b-6863-4fa6-fde088ec6695.png)

### 結果

期待通りJSの実行が前倒しされたからか、TBTが改善されましたが、FCPとLCPが悪化してしまっています。
全体ではスコアは上がっているのですが、変動が大きくなったためこの変更は後に元に戻すことにしました。

| |スコア|FCP|SI|LCP|TTI|TBT|CLS|
|:----|:----|:----|:----|:----|:----|:----|:----|
|1回目|66|1.9|4.1|2.3|11.3|750|0.001|
|2回目|71|1.9|4|2|10.4|630|0.001|
|3回目|65|1.9|4.1|2|12.1|860|0.001|
|平均|67.333|1.900|4.067|2.100|11.267|746.667|0.001|
|直前との比較| <font color="blue">113.48%</font> | <font color="red">126.67%</font> | <font color="blue">81.88%</font> | <font color="red">123.53%</font> | <font color="blue">90.86%</font> | <font color="blue">57.14%</font> |12.50%|
|最初との比較|404.00%|44.19%|38.98%|27.51%|72.07%|43.84%|1.42%|

## 画像のWebP化

### 概要

コラム記事ページでは、歴史的経緯のために、画像ファイルがPNG形式で保存されていました。
写真も多いので本当はPNGではなくJPEGで良いと思うのですが、ちょうどWebPのネイティブサポートも増えてきていたのでせっかくならとWebPを使ってみることにしました。

これに伴い大量の画像を変換しないといけないところだったのですが、Lambda@Edgeで対応した事例もあったのでありがたく乗っかることにしました。

https://techlife.cookpad.com/entry/2018-05-25-lambda-edge

beforeはメイン画像が3.5秒らへんで読み込まれている
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31474/2047ab36-fe28-6fcd-ddbf-6056b9509b7f.png)

### 結果

Speed Index、Largest Contentful Paintは改善傾向にありますが、スコア自体は悪化しています。
この悪化は前述の広告のpreloadのためにスコアが不安定になっていることによると考えています。

スコアの解釈に迷う部分もありつつも、WebP化により速度制限のあるユーザでもスムーズにページを閲覧できるようになり、UXは向上しているはずなので変更自体はそのままにすることにしました。

なお、同じ時期にCSSファイルを切り出すリファクタリングも実施していますが、広告のpreloadもrevertした上で、後日WebP化だけの有効・無効を切り替えたときは、5点前後の改善となっていました。


| |スコア|FCP|SI|LCP|TTI|TBT|CLS|
|:----|:----|:----|:----|:----|:----|:----|:----|
|1回目|61|1.9|3.5|2|11.8|1410|0.001|
|2回目|63|1.9|3.7|2|11.8|1040|0.001|
|3回目|62|1.9|3.6|2|11.3|1280|0.001|
|平均|62|1.9|3.6|2|11.63|1243.33|0.001|
|直前との比較| <font color="red">92.08%</font> |100.00%| <font color="blue">88.52%</font> | <font color="blue">95.24%</font> |103.25%| <font color="red">166.52%</font> |100.00%|
|最初との比較|372.00%|44.19%|34.50%|26.20%|74.41%|72.99%|1.42%|


メイン画像の読み込みが2秒くらいに早まった

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31474/20e7ec88-31bc-f5be-5ace-e5e71ae0f4ff.png)

ある画像ではPNGだと220KBだったのが21.8KBに
![ダウンロード (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31474/b39d053e-736a-0473-b933-eb3564c974f4.png)

ちなみに現在はLambda@Edgeはやめて記事入稿管理画面での画像アップロード時にWebP版を生成するようにしています。

## Googleアドマネージャー広告のpreloadをやめる

### 概要

Googleアドマネージャー広告のJSの読み込みを早めたことで、諸々のリソースがスムーズに読み込まれた場合は点数が上がるのですが、スムーズに読み込めなかった場合は点数が悪化しているように見えます。変動が大きくたまに大幅改善するよりは、小幅でもいいから重要度の高い部分で安定的に改善しておきたいと考えた結果、preloadは元に戻すことにしました。

### 結果

LCPが低いところで安定するようになり、スコアも安定して60点台を出せるようになりました。
TBTは悪化してしまいましたが、重いJSを読み込んでいる以上は仕方がないだろうということで、ここは許容した上でFCPとLCPを優先して改善しようという判断です。

| |スコア|FCP|SI|LCP|TTI|TBT|CLS|
|:----|:----|:----|:----|:----|:----|:----|:----|
|1回目|68|1.4|3.2|1.4|11.4|880|0.001|
|2回目|65|1.4|3.3|1.5|11.5|1110|0.001|
|3回目|61|1.4|3.4|1.5|12.2|2550|0.001|
|平均|64.67|1.40|3.30|1.47|11.70|1513.33|0.001|
|直前との比較| <font color="blue">104.30%</font> | <font color="blue">73.68%</font> | <font color="blue">91.67%</font> | <font color="blue">73.33%</font> |100.57%| <font color="red">121.72%</font> |100.00%|
|最初との比較|388.00%|32.56%|31.63%|19.21%|74.84%|88.85%|1.42%|


## できるとよかったけどやらなかったこと

### Facebook Pixelが4つ？5つ？入ってるのを消す

他事業部との諸々の関係でFacebook関連の同じようなJSが何個も読み込まれているのですが、試しに減らしてみても点数があまり変わらなかったのと、運用を一本化して一つだけにしようというのもコミュニケーションが大変そうだったのでやめました。

### Googleアドマネージャーの枠をlazyloadにする

公式のオプションでlazyloadにできるようなことが書いてあったので試してみましたが、点数が変わらなかったのでやめました。
改善できたとしても、それを実施した場合に「インプレッション」の定義が変わってしまうと関係各所との調整が必要になるため、慎重な作業が必要なところだったと思います。

他社事例でJSファイルの読み込みからゴニョゴニョする例もありましたが、上記の理由で数値の信頼性信ぴょう性に関わってくるのでやめました。

### 本当はアドマネージャー広告を消したかったが...

そもそもGoogleアドマネージャーの広告枠を消すと一気に10点ほど改善できるというのはあったのですが、他グループでの広告運用の都合上、すぐには廃止できないということになりました。
アドマネージャーと同じくらい便利な広告入稿管理画面を自力で作るのも骨が折れるので、これは致し方ないというところです。

ちなみに広告がないと動作が遅めのstaging環境でも70点台に入れました

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/31474/f7a2ad5e-9aba-7c64-f7bd-c1a1cfa63ec3.png)


# 最後に

以上、色々と改善を重ねた結果、最低13点から最高68点まで点数を上げることができました。当時としてはある程度工数もかけたことだし、このへんで様子を見ようということになりました。


| |スコア|FCP|SI|LCP|TTI|TBT|CLS|
|:----|:----|:----|:----|:----|:----|:----|:----|
|着手時点の平均|16.67|4.30|10.43|7.63|15.63|1703.33|0.07|
|作業完了時点の平均|64.67|1.40|3.30|1.47|11.70|1513.33|0.001|
|最初との比較| <font color="blue">388.00%</font> | <font color="blue">32.56%</font> | <font color="blue">31.63%</font> | <font color="blue">19.21%</font> | <font color="blue">74.84%</font> | <font color="blue">88.85%</font> | <font color="blue">1.42%</font> |



どのスコアを優先して上げるのか・変動は大きいが大きな改善をとるか変動の小さい中規模の改善をとるのか・かけられる工数はどこまでか・運用上事業上許容できる変更内容はどこまでかなど、色々と頭を使う奥の深い作業で面白かったです。

ちなみに現在は下記のような便利な記事もあるので、こちらを参考にチューニングを積み重ねていくのも良いと思います。

https://qiita.com/nuko-suke/items/50ba4e35289e98d95753

---

明日は　@katzumi さんの記事です。お楽しみに！