パフォーマンス改善についての連載をしています!
QiitaでWebパフォーマンスの改善に取り組みはじめました!
そこで、どんな観点で分析をして、どう改善しているのかをシリーズとして投稿しています。
よろしければ他の記事も読んでみてください!
今回はLCPの改善についての説明をします。
シリーズ内容(予定)
- 【Core Web Vitals】Webパフォーマンスを改善するなら、まずは重要な指標について理解しよう
- Lighthouse CI + Datadogを使ってパフォーマンスを継続的に計測する
- 【番外編】開発者ツールのLighthouseを使いこなせてますか?
- LCPを改善する【←今回はこれ】
- FIDを改善する
- CLSを改善する
- キャッシュについて
もし興味のある方は、この記事をストックしていただくか、Twitter(@kyntk_1128)をフォローしていただければ記事の更新ごとに通知しますので、ぜひよろしくお願いします!
この記事で説明すること
さて、Core Web Vitalsの説明の記事でLCPについての概要を説明しました。
また、LCPを始めとしたCore Web Vitalsの具体的な計測方法についても継続的に計測する方法の記事で、説明しました。
今回はこれらを元に、実際にLCPを改善する当たりをつけられるように、ボトルネックの見つけ方を順を追って説明していきます。
Lighthouseの結果に全部出てるけどこれじゃだめなの...?
1つ前の番外編の記事でLighthouseの使い方について説明しました。
Chromeの開発者ツールでLighthouseを使うだけで、改善すべき点がすでにリストアップされています。
そして、「LCP」のボタンで絞り込むと、LCPに絞った改善ポイント記載されています!!!
もうこれを上からやっていけばいいのでは?と思うのですが、やってみると意外と効果が出ないということもあります。
LCPの改善といっても、サイトごとにボトルネックは異なるためです。
きちんとサイトの特性を理解した上で、Lighthouseの提案ポイントのうちどれを優先してやるかを決める方が近道です。
最初のステップ LCPまでの流れを把握する
そのため、手当り次第に改善する前に、LCPまでの間にブラウザ上で何が行われているのかを把握することが大事だと考えています。
具体的に知っておきたいのは、大きく以下の2つです。
- 一般的にブラウザがレンダリング・描画するまでにどんな順序で何をしているのかを知る
- 自分のサイトは、どの要素でLCPが判定されていて、その要素の描画までに何が行われているかを把握する
1については色々なサイトや本などで説明されているのでこの記事では省略し、2について説明していきます。
ただ、2について調査をする上で、そもそもの前提が分かっていないと難しいこともあるので、理解を深めておくことが重要です。
1について学習するには、こちらなどを参考にするといいと思います。
サイトの分析 LCPの要素を知る
まず最初のステップは、「どの要素の描画がLCPと判定されているか」を把握することです。
これについてはChromeの開発者ツールのPerformanceタブを使うと、LCPの要素をハイライトしてくれるので、知ることができます。
要素が分かったら、次はこの要素をいかに早く表示させるかを考えていきます。
その要素が描画されるまでの流れを知る
ここでは単純化してLCPまでの流れを考えていきます。
改善したいサイトはどれに当てはまるのかを考えてみましょう。
① SSR + テキスト
まずは最もシンプルなパターンを考えてみます。
HTMLをサーバーサイドレンダリング(SSR)し、LCPとなる要素がテキストの場合です。
Static Generationは正確にはSSRとは別ですが、簡略化のためにこのケースに含めて考えても当てはまることがあるでしょう。
先程のQiitaのトレンドページはこのパターンに該当します。
単純化すると以下のようになります。
最初のリクエストで帰ってきたHTMLをパースすると同時に、HTMLに記述されているCSSを取得しに行きます。
CSSが取得できると、CSSのパースを行い、CSSOMを構築します。
これらを元にレンダーツリーを構築し、画面にテキストが描画され、LCPとなります。
Qiitaのトレンドページではこれ以外にもJavaScriptを取得したり、画像を取得したり、JavaScriptによってコンテンツが生成されていたりします。
これらはページを構築するには必要ですが、LCPまでの最短ルートに必須ではありません。
にも関わらず、これらによってLCPのスコア悪化につながることがあるので、できる限り最短ルートを阻害しないようにすることも必要となります。
また、今回は説明を省きますが、fontのswapなどによって、テキストが最初に表示された時にLCPが計測されないこともありますので注意が必要です。
② SSR + 画像
次はLCPとなる要素が画像やvideo要素などのパターンです。
テキスト以外のサブリソースがLCPになるパターンを考えます。
ここも比較的シンプルで、画像を取得して、それが描画完了するとLCPとなります。
テキストと違って、画像のリクエストが発生するので、画像のサイズや、画像を取得し始めるタイミングも重要になってきます。
③ CSR + テキスト
①と違う点としては、LCPとなるテキストが返却されるHTMLには含まれておらず、JavaScriptによってクライアント側で生成されるという点です。
あくまで、LCPの要素がクライアントサイドレンダリング(CSR)をしているパターンで、一部だけCSRしているものもこちらに含めます。
LCPはJavaScriptの取得、評価、実行をした後に評価されます。
単純なテキスト生成だけでなく、計算が含まれる可能性もあり、ロジックの複雑さなども影響してきます。
④ CSR + 画像
今回説明する最後のパターンです。
②と同様に画像の描画がLCPとなるパターンですが、②と違い、JavaScriptで動的に画像のパスが決まるケースなどが該当します。
このパターンには、クライアントサイドで画像のパスを取得するために、別のリクエストを行ったりすることも考えられ、LCPまでに必要なステップが多くなってきます。
そのような複雑なケースでもでもステップを1つずつ分解していくことで、順番にシンプルに解決していきます。
このように、LCP改善までのステップを逆算するためにも、自分のサイトがどのようなステップを踏んでいるのかを把握することが重要となります。
具体的に分析していく
さてここからは、LCP改善のために、どこに問題があるのかを確かめていきます。
主に開発者ツールのPerformanceタブで見ながら確認していきます。
また、Performanceタブだけでなく、Networkタブも併せて使っていきます。
PerformanceタブでもNetworkが表示されており、LCPが発生したタイミングや、メインスレッドの処理の詳細などと併せて分析することができます。
ただ、リクエストごとのレスポンスタイムなどの詳細を確認したいときはNetworkタブの方が便利な時もあるので、組み合わせて使っていきます。
Performanceタブで見るところ
Performanceタブは多機能で様々な分析をできるのですが、その分どこを見れば良いのかが分からなくなってしまうことがあるかもしれません。
大まかな流れを知るために必要な、見るポイントを解説します。
1. TimingsでLCPのタイミングを把握する
Performanceを計測したら、Timingsで「LCP」のボックスをクリックしてLCPの要素とタイミングを把握します。
そして、分析するタイムスパンをLCP直後くらいまでに絞ってしまいます。
こうすることで、LCPまでに発生していることが分析しやすくなります。
2. Networkで、どのようなリクエストが発生しているかを見る
Networkを確認することで、どのようなリクエストが発生しているかを知ることができます。
- 青: HTML
- 紫: CSS
- 黄: JavaScript
- 緑: 画像
と色がついているので、どのタイミングでどのリクエストが発生しているのかがわかります。
また、それぞれのバーの長さで、リクエストにかかる時間、ダウンロードにかかる時間が表現されているので、この長さも重要です。
3. Mainでメインスレッドが何に時間を使っているのかを知る
Mainを見ると、メインスレッドがどれにどのくらい時間を使っているのかがわかります。
これを見ながら、Long Taskになっているところは無いかなどを確認します。
実際に改善をしていく時にはもっと詳しく分析しますが、このあたりを見ておけば一旦どこに課題がありそうかというのは分かってくると思います。
パターンごとのフォーカスポイント
ここから先程紹介した4パターンごとに、Peformanceタブのどこにフォーカスして分析するといいかを説明していきます。
① SSR + テキスト
まずは最もシンプルな①から分析していきます。
先程Lighthouseのレポートを上から順にやるのでは無駄が発生する可能性があると述べました。
例えばこのパターンでは「画像」が出てこないというところから、もし画像の最適化などを頑張っても、直接はLCPのスコア改善につながらない可能性が高いことがわかります。
ではどこを見ればいいのでしょうか。
1. HTMLの取得まで
スタートはHTMLの取得です。HTMLが返却されて初めて次の処理に進めます。
ここはNetworkタブを見ると分かりやすいです。
TTFB(最初の1バイトを受信するまでの時間)や、Content Downloadにかかっている時間が、LCPまでのうちどのくらいを占めているかをチェックします。
サーバーでの処理が重かったり、HTMLのサイズが大きくてダウンロードに時間がかかったり、評価に時間がかかったりしていると、これを改善することで効果があるかもしれません。
具体的な改善策はサーバーの構成などの様々な要素によって多岐に渡るので、この記事では省略します。
2. ネットワークのリクエスト回数、サイズ
レンダリングの前にCSSの読み込みが完了しないといけません。
そのため、取得するCSSファイルの数が多かったり、サイズが大きかったりして時間がかかると、その分レンダリングをブロックしてしまいます。
ここに課題があるときには、サイズを小さくしたり、圧縮したり、適切にバンドルしたり、HTTP2を使って取得を効率化したりすることで改善が見込めます。
また、CSSは別ドメインのCDNから配信したりすることもあるのですが、事前にpreconnectをしておくとオーバーヘッドを減らせたりします。
CSSの取得のリクエストを省略するために、インラインでスタイルを記述する方法もありますが、キャッシュ・再利用ができなくなるデメリットもあるので、比較検討が必要です。
3. その他のリソースがレンダリングをブロックしていないか
例えば、JavaScriptが同期的に取得されたりしていると、レンダリングをブロックしてしまいLCP悪化につながったりします。
それ以外に直接ブロックをしないものの、ブラウザのCPU・メモリなどのリソースに影響を与えるので、Above the fold(スクロールせずに見える部分)に必要なもの以外は読み込まないこともプラスに働きます。
例えばJavaScriptをダイナミックインポートしたり、画像をlazy loadしたりといった遅延読み込みなどです。
その他に、キャッシュできるものはキャッシュすることも効果的で、CDNや、NginxなどのWebサーバーによるリバースプロキシ、ブラウザでのキャッシュなどでHTML、CSS、JSをキャッシュする方法もあります。
ここまで色々チェックポイントが出てきましたが、LCPに関してでいうと、総じてCSRよりはシンプルになるので、SSRしておくとスコアは改善される傾向があります。
② SSR + 画像
②のパターンでは①に加えて画像の取得が発生します。ここでチェックするのはこちらです。
- 画像を取得し始めるタイミング
- 画像の取得完了までの時間
画像をレンダリングできれば勝ちなので、いかに早いタイミングで取得しはじめて、すぐにダウンロードができるかが勝負です。
タイミングを早める方法としては、画像のpreloadなどがあります。
注意点としては、対象の画像がCSSのbackground image等で使われていると、そもそも取得のタイミングが遅くなるので、より改善が重要になります。
画像の取得スピードに関しては、画像のファイルサイズが影響してきます。
適切な画像フォーマットを使ったり、圧縮したり、レスポンシブイメージを使ったり、画像CDNなどを使い、画像のサイズを必要最低限にすることで改善が見込めます。
詳しくはOptimize your imagesに方法がいろいろ乗っています。
③ CSR + テキスト
ここからはJavaScriptによってクライアントサイドでレンダリングされるケースです。
①と違い、アプリケーションごとのJavaScriptの実装により、テキストを表示するにも様々な可能性があるので一概には言えず、個別に分析をして改善をすることが必要になってきます。
とはいえ、一般的に影響する可能性が高いのはこれらです。 (もちろん①で挙げた要素も影響してきます)
1 JavaScriptのサイズが大きくなっていないか
2. LCPまでに不要なレンダリングをしていないか
3. 処理の実行タイミングは適切か
4. その他時間のかかる処理をしていないか
1. JavaScriptのサイズが大きくなっていないか
JavaScriptのサイズが大きくなると、ダウンロードや、実行前の評価に時間がかかってしまいます。
ここでもファイルサイズは重要になってきます。
JavaScriptのファイルサイズ削減はいろいろな要素が絡んでくるので、webpack-bundle-analyzerなどを使いながら地道につぶしていくことになります。
- 不要なコードの削除
- minify
- 圧縮
- 不要なポリフィルの削除
- ブラウザが対応した最新のECMAScriptのバージョンで記述する
- サイズの大きいライブラリを使わない
- Tree Shakingを活用する
などといったことが関係してきます。
2. LCPまでに不要なレンダリングをしていないか
Above the fold外にあるものはレンダリングを遅らせることで、LCPまでの改善につなげることができます。
レンダリングのタイミングを遅らせたり、content-visibilityなどを使うことで、不要な処理を減らすことができます。
3. 処理の実行タイミングは適切か
Reactだと、useEffectを使って、マウント後に処理をして、その結果を表示させたりすることができます。
これに限らず、レンダリングのタイミングが不必要に遅くなっていることは無いかチェックします。
非同期で実行させていたり、不要にロードを待ったりすると遅くなることがあります。
4. その他時間のかかる処理をしていないか
最後はざっくりしていますが、JavaScriptで重い計算をしていたり、APIを叩いていたりするとその分遅くなります。
これをいかに軽量化できるか、事前にfetchできないかを検討します。
LCPに不要なところであれば非同期で行ったり、workerを使ったりすることで、メインスレッドの仕事量を減らせます。
④ CSR + 画像
ここは①〜③の組み合わせです。
このケースで特にフォーカスするポイントがあるというよりも、①〜③にある要素を順に分解していき、どこに課題がありそうかを見極めることが必要です。
ステップが増えても、LCPとなる画像をいかに早く取得して、それ以外の処理が邪魔をしないようにすることは変わりません。
複雑度が増して、難易度が高いですが、1つ1つ解決していくことが必要となります。
その他、参考になりそうな資料
ちょうど先日のGoogleIO2022にて、LCPを改善するステップを1つ1つ丁寧に説明してくれる動画が公開されていました。
とても分かりやすかったので、あわせてご覧ください。
まとめ
今回は、LCPを改善するためにフォーカスポイントを見つける方法を説明しました。
最初から解決策に飛びつくのではなく、まずサイトのパフォーマンスの計測と分析をすることが大事だと伝わったでしょうか?
この記事では、解決方法について紹介することはしなかったので、詳しく知りたい方はweb.devにたくさん紹介されているので、こちらをご覧ください。
このシリーズはまだまだ続く予定ですので、LGTMとストック、Twitter(@kyntk_1128)での続報をお待ち下さい!