公開日:

Core Web Vitalsの実践的な改善術 - LCP編

Authors
  • author image
    Name
    代表取締役 宮永邦彦
    Twitter
    @miyanaga
  • 画像軽量化とWebフロントエンドのスピード改善の専門家です。Web系のIT技術大好き。

    このサイトではスピード改善のリアルや、日々の技術的な気づきを共有します。

Core Web Vitalsのひとつ、LCP(Largest Contentful Paint)の改善手法について解説する。

同じCore Web VitalsのCLS(Cumulative Layout Shift)は丁寧にコーディングを行えば改善できるが、それに比べてLCPの改善は難しい。

実際にSEOの文脈でCore Web Vitalsが話題になった2021年〜2022年ごろ、CLSの改善は多くのサイトで見られたが、LCPまで改善できたサイトは少ない。

加えて、ネットの記事にはLCPの改善について誤解が多い。よく見られるのが画像の軽量化であるが、画像データがLCP悪化の主な要因であるケースなどほとんどない。

この記事ではLCP改善の実践的な手法を紹介したい。


誤解だらけのLCP改善

LCPは、ビュー(主にファーストビュー)において最大要素(テキストまたは画像)が表示されるまでの時間 である。

メディアサイトの記事のようなページであれば見出しテキストがLCPの判定対象になることが多い。それ以外のページ上部にはメインビジュアルに相当する大きな画像がある。その画像がLCPの判定対象になる。

画像が重いからLCPが悪い?

LCPの判定対象になるのは画像だから、LCPの改善には画像を軽量化がよいと解説するネット上の記事は多い。しかしそれは短絡的すぎる。

もちろん画像を軽量化するに越したことはないが、それだけでLCPを大きく改善できるケースなど現実にはほとんどない。

なぜなら、画像の読み込みが始まる時点で勝負はついているからだ。画像がいくら軽かろうが、時間は巻き戻せないのだ。

LCP = FCP + 最大要素の遅延

Core Web Vitalsには含まれないがLCPに似た指標にFCP(First Contentful Paint)がある。文字通り、

  • LCP 最大要素の表示時間
  • FCP 最初の要素の表示時間

であるが、論理定に考えても最大要素の表示が、最初の要素の表示より先に来ることはありえない。したがって、

$LCP ≧ FCP

であり、より具体的に言えばFCPを達成後、最大要素が表示されるまでの遅延を経てLCPの達成となる。

$LCP = FCP + 最大要素の遅延

LCPの対象がテキスト(大見出しなど)の場合、FCPとLCPは同じ値になることがある。

データでも確認できる LCP ≧ FCP

LCPがFCPを下回ることがないことはデータでも確認できる。

以下のグラフは、複数の通販サイトから無作為に1000ページを抽出し、PageSpeed Insightsを実行した結果を元に作られている。

散布図に、LCP対数とFCP対数の関係を示す矢印が描かれている図

横軸にFCP、縦軸にLCPをともに対数軸としてプロットした散布図であるが、見ての通り右下の三角形の領域にはサンプルが存在しない。

右下の領域はLCPの値がFCPを下回ること意味する。そのような例はひとつもないという意味である。

LCPが悪いケースは3パターン

これを踏まえるとLCPの値は4つのパターンに分類される。

  • FCPもLCPもよい この場合は対応不要である。
  • FCPは良いが、LCPは悪い 最大要素の遅延に問題がある。
  • FCPが悪く、LCPも同じくらい悪い FCPに問題がある。
  • FCPが悪く、LCPはもっと悪い FCPと最大要素の遅延の両方に問題がある。

経験的にFCPは良いのにLCPは悪いケースは少ない。そのような理由でLCPの改善はまずFCPからの対処が必要になる。

画像の軽量化がLCPに効くという説は、「テスト勉強には一夜漬けがよい」と言っているのと大差ないのだ。

FCPを改善するには

FCPをよい値に保つ秘訣は、スタートダッシュはHTMLとCSSに全振りすることである。

これは何も特別な話ではなく、ブラウザは本来そのように動作するように作られている。その足を引っ張ったり、邪魔をする要素があるからFCPが悪化する。

よくあるケースは以下である。多いがひとつずつ見ていこう。

  • HTMLとCSSが圧縮されていない
  • HTMLとCSSが過剰に大きい
  • CSSの読み込みにばらつきがある
  • CSSがHTMLと異なるドメインから配信されている
  • CSSで@importが用いられている
  • 不自然なpreloadが指示されている
  • JavaScriptが乱入する

ケース: HTMLとCSSが圧縮されていない

テキストリソースは必ず通信中にGzip等で圧縮する。素のままでは通信量が何倍も大きくなってしまう。

ケース: HTMLとCSSが過剰に大きい

ブラウザは内部で、HTMLとCSSを元に画面に何を表示するか詳細な設計図を描く(レンダリング)。

この計算量は HTML(DOM)の大きさ × CSS(CSSOM)の大きさ となる。両方が大きいとCPUの処理時間が膨張する。

そのページで使用しないCSSプロパティもCPUを消費する。CSSはそのページで使用しないプロパティは含まないようにするのが理想である。

ケース: CSSの読み込みにばらつきがある

ブラウザはHTMLとCSSから設計図を描くが、一部のCSSが遅れて到着すると「ちゃぶ台返し」が起こる。

それまで書いた設計図を破り捨てて、また書き直すような無駄が生じてしまう。

CSSを1ファイルにまとめるとこのような後出しによるやり直しを回避できる。並列ダウンロードは効かなくなるが、CSSは通信量がボトルネックになるようなサイズではないので、個人的には有用な場面の方が多いと感じる。

ケース: CSSとHTMLと異なるドメインから配信されている

CSSのドメインがHTMLと異なると、DNSルックアップとSSLハンドシェイクで一手遅れる。

これらは通常は瞬間的に行われるが、ドメインが同一である方がスムーズであるし、PageSpeed Insightsの計測ではFCPを0.5秒ほど違う。

ケース: CSSで@importが用いられている

スピードの観点からいうとCSSの@importは禁じ手である。

ブラウザはCSSの読み込みをできるだけ並列で急ぐのだが、@importによって別のCSSの存在がわかるとタイムラインが直列に間延びしてしまう。

ケース: 不自然なpreloadが指示されている

link要素にrel="preload"属性を指定することで、指定したリソースの先読みができる。

ただ、先読みと言っても魔法のように事前処理されたり、何もないところに優先経路が現れるわけではない。単にダンロード順に「割り込み」をするだけだ。

割り込みされたリソースは逆に読み込みが遅れる。読み込みが遅れるリソースは何か。CSSである。したがってpreloadはよほど特殊な理由がないかぎり推奨しない。

なお、CSSをpreloadに指定している例も見かけるが、心配しなくてもCSSは最優先に読み込まれるのでその記述は無意味である。

ケース: JavaScriptが乱入する

JavaScriptはWebページにおいて要件定義を書き換える強大な権限を持っている。そのためJavaScriptの存在を確認するとブラウザは設計図作り(レンダリング)を一時中断してしまう。

日常的なイメージに例えると、現場が向け全力で仕事をしているところに社長が首を突っ込んできて納期が遅れるような話だろうか。JavaScriptはFCPに関して言えば邪魔者なのである。

そのためscript要素はasyncdeferの属性によりレンダリングの中断を回避したり、HTMLの下部に記述してスタートダッシュをレンダリングに全振りするように務める。

問題を特定するには

このようにFCPの阻害要因はさまざまだが、開発者ツールのPerformanceタイムラインを穴が開くように調べ、FCPの前に何が起きているか突き詰めていけば必ず答えは見つかる。

ChromeのPerformanceタブのスクリーンショット、ネットワークリクエストとレンダリングのタイムラインが表示されている

なお、Performanceタイムラインを実行するときは、CPUとNetworkの性能制限を設定するとよい。PC環境のCPU性能だとボトルネックが過小評価されてしまい、発見が難しくなるからだ。

ブラウザのパフォーマンス測定結果、CPUとネットワークの速度、拡張機能データが表示されている

最大要素の遅延を改善するには

次はFCPとLCPの差をもたらす最大要素の遅延を解決する。最大要素に遅延があるケースではほぼ間違いなくLCPの対象要素は画像である。

以下のケースが考えられる。

  • メイン画像のデータが極端に大きい
  • メイン画像の読み込み開始が遅い
  • 非表示の画像が優先的に読み込まれている
  • メイン画像の表示がJavaScriptに依存している

ケース: メイン画像のデータが極端に大きい

画像軽量化によりLCPを改善できるケースはほとんどないと書いたが、もちろん可能性はゼロではない。

メインビジュアル画像が極端に大きなデータサイズで、その画像の軽量化でLCPを改善できるケースもある。

ケース: メイン画像の読み込み開始が遅い

画像データは基本的にHTMLの上部に記述されたものから読み込みが始まる。

そのため、メインビジュアル画像より上の例えばヘッダ領域の画像が優先されてしまう。

画像はCSSで実際には非表示であっても関係なく読み込みが始まる。以前実際にあったケースでは、スマホ向けのサイドメニューの中に多数の画像があり、それらがメインビジュアル画像の読み込み開始を遅らせていた。

そこで、img要素のloading属性を用いる。HTML上では上部にあっても、loading="lazy"を指定するとその画像の読み込み優先度は下げられる。逆にloading="eager"を指定すると優先度を上げられる。

以下のように、メインビジュアル相当の画像にはloading="eager"、それよりHTMLの上で上部にある画像にはloading="lazy"を明示することでメインビジュアルの読み込み順を優先できる。

html
<!-- 中略 -->
<img src="header.jpg" loading="lazy">
<!-- 中略 -->
 <img src="mainvisual.jpg" loading="eager">

ケース: 非表示の画像が優先的に読み込まれている

レスポンシブウェブデザインでは、メインビジュアルがスマホ向けとPC向けで二種類用意されることがある。

当然、スマホとPCで出し分けることになるが、この制御をCSSで行っている事例を散見する。

html
<img src="mainvisual/sp.jpg" class="sp">
<img src="mainvisual/pc.jpg" class="pc">

上記の記述では、画像データのダウンロードは両方に対して発生してしまい、必ずどちらかが無駄になる。

デバイスによる画像の出し分けにはCSSではなく、srcset属性やpicture要素を利用するべきである。この方法であれば無駄なダウンロードが生じない。

メインビジュアルはデータサイズが比較的大きい。せめてメインビジュアルだけでも、適切なレスポンシブ画像技術でコーディングすべきである。

ケース: メイン画像の表示がJavaScriptに依存している

よくあるのはメインビジュアルがカルーセルスライダーになっており、JavaScriptの当該コードが起動することで初めて表示されるケースだ。

JavaScriptによる画像の遅延読み込み制御(いわゆるLazyload)が適用されているケースもある。なお、JavaScriptによるLazyloadは今や害の方が大きいので即刻止めるべきである。

以前のCLSの改善についての記事で、カルーセルスライダーのレイアウト安定化を紹介した。

詳しくは上記の記事を参照いただきたいが、要は JavaScriptが起動する前からカルーセルスライダーの1枚目の画像が見えるようにコーディングする という対策である。一時的にJavaScriptを無効にしてデバッグするとよい。

JavaScriptの実行タイミングはページ読み込みの後半のタイミングになることが多い。そこまで待たないとメインビジュアルが表示されない仕様は、LCPに対して大変不利な条件となる。

カルーセルスライダーの1枚目の画像だけ、HTML・CSS・画像の技術要素だけで表示を担保すると、最大要素の遅延を最小化できる。

Performanceタイムラインを見る

これらのボトルネック調査も、FCPと同様に開発者ツールのタイムラインにすべて答えが現れる。

PageSpeed Insightsの占いじみた指摘事項に従うより、タイムラインを眺める習慣を身につけよう。

まとめ

Webページも一種のプログラムであるので、思った通りには動いてくれない。書かれている通りに動く。

一般的なプログラムと違うのは、Webページは多数のリソースがベストエフォートで協調しながら目的を果たすところだ。書かれている通りには動くが、何がどのタイミングで起こるかは実際に観察してみないとわからないことが多い。

LCPの改善はそのような不確実さとの戦いであり、それゆえに難しい。

この記事で解説したようにFCPの問題と、最大要素の遅延の問題に分解して個別に解決するアプローチは必ずや打開策になるだろう。