Core Web Vitals 徹底解説
LCP・INP・CLS を理解して改善する——開発者目線のデバッグワークフロー付き。
Core Web Vitals は、Google が ページ体験 を測定するために標準化した指標です——ページがどれだけ速く描画されるか、入力にどれだけ素早く反応するか、読み込み中にどれだけ安定しているか。これらは、より広いページ体験シグナルへとまとめ上げられ、ランキングではタイブレーカーとして機能します。優れたコンテンツを上回ることはめったにありませんが、遅くてガタつくページは、同じくらい関連性の高い速いページに負けてしまいます。
現時点で Core Web Vitals はちょうど 3 つあり、そのうち 1 つは新しいものです。2024 年 3 月以降、応答性の指標として INP(Interaction to Next Paint)が FID(First Input Delay)に取って代わりました。FID は 最初の インタラクションが処理されるまでの遅延だけを測っていましたが、INP はページのライフタイム全体にわたるインタラクションのレイテンシ全体を測ります。頭の中のモデルがまだ「FID」のままなら、更新してください——FID は非推奨となり、もはやレポートされません。
このガイドは、コードが読めて変更をリリースできることを前提としています。指標を 1 つずつ見ていき、本当に重要なデータソースを切り分けたうえで、どんなページにも適用できる具体的なデバッグループを順を追って説明します。
3 つの指標
各指標には 3 つのしきい値バケットがあります。Google は実ユーザーの 75 パーセンタイル を評価します——つまり、開発マシンでの中央値の体験ではなく、4 回のページ読み込みのうち 3 回が「良好(Good)」のラインを超える必要があります。
| 指標 | 測定対象 | 良好(Good) | 改善が必要(Needs improvement) | 不良(Poor) |
|---|---|---|---|---|
| LCP(Largest Contentful Paint) | 最大の可視要素(ヒーロー画像、見出し、動画のポスター)が描画されるまでの時間 | ≤ 2.5 秒 | 2.5 – 4.0 秒 | > 4.0 秒 |
| INP(Interaction to Next Paint) | ユーザーのインタラクションから次のフレームが描画されるまでの最悪ケースのレイテンシ | ≤ 200 ms | 200 – 500 ms | > 500 ms |
| CLS(Cumulative Layout Shift) | ページのライフタイム中に発生した予期しないレイアウトシフトスコアの合計 | ≤ 0.1 | 0.1 – 0.25 | > 0.25 |
頭の中に留めておくのに便利な言い方はこうです:LCP は「もう表示された?」、INP は「つついたら反応した?」、CLS は「読んでいる間に動いた?」。読み込み、応答性、視覚的安定性です。
🧑💻 開発者視点:CLS は無次元です——ミリ秒ではなく、シフトのウィンドウ全体にわたって合計した
impact fraction × distance fractionです。ビューポート上部付近での 1 回の大きなシフトだけで予算全体を吹き飛ばすことがある一方、折りたたみ位置より下での無数の小さなシフトはほとんど影響しないこともあります。
フィールドデータ vs ラボデータ
この区別は、どの個別の最適化よりも多くのチームをつまずかせます。測定には 2 種類あり、それぞれ異なる問いに答えます。
ラボデータ は、制御された環境での合成的な実行から得られます——Lighthouse、WebPageTest、または Performance パネルです。1 つのデバイス、1 つのネットワークプロファイル、1 回のコールドロード。再現性があり、デバッグには最適ですが、これは 1 サンプル であり、INP を正しく測定することはできません。INP には、実際のセッションを通じて実際のものをクリックする実際の人間が必要だからです。Lighthouse は応答性のラボ代替指標として Total Blocking Time(TBT) を使います。
フィールドデータ は、Google が実際にランキングに用いるものです。これは Chrome User Experience Report(CrUX) から得られます——レポートにオプトインした実際の Chrome ユーザーからの匿名化・集計された指標で、ローリングの 28 日間ウィンドウでバケット化されます。これは惑星規模の「RUM(Real User Monitoring)」です。
| ラボ(Lighthouse) | フィールド(CrUX / RUM) | |
|---|---|---|
| ソース | 合成的な単一実行 | 実ユーザー、28 日間ウィンドウ |
| INP 対応 | なし(TBT 代替を使用) | あり |
| ランキングに使用 | いいえ | はい |
| 適した用途 | デバッグ、CI ゲート | グラウンドトゥルース、優先順位付け |
| ばらつき | 低い(制御済み) | 高い(実デバイス/ネットワーク) |
⚠️ 注意:Lighthouse のスコアが緑だからといって、Core Web Vitals が「良好」とは限りません。Lighthouse はスロットリングされたものの「クリーン」なシミュレーションデバイス上で実行されます。実際のユーザーには、不安定なネットワーク上のミドルレンジ Android スマートフォンも含まれます。勝利を宣言する前に、必ずフィールドデータで確認してください。
実践的なルール:ラボでデバッグし、フィールドで判断する。 Lighthouse は(決定論的なので)問題を素早く見つけて修正するために使い、Search Console や PageSpeed Insights の CrUX を、修正が効いたかどうかの真実の拠り所として扱いましょう。
LCP を改善する
LCP は最も分解しやすい指標です。LCP までの時間を 4 つのサブフェーズに分解すれば、どこに労力を割けばよいかが正確にわかります。
| サブフェーズ | 起きていること | 遅い LCP に占める典型的な割合 |
|---|---|---|
| TTFB | サーバーが最初のバイトを返す | しばしば 40% 以上 |
| リソース読み込み遅延 | TTFB から、ブラウザが LCP リソースの読み込みを 開始する までの時間 | 最も多い隠れた元凶 |
| リソース読み込み時間 | LCP 画像/フォント/動画のダウンロード | ネットワーク依存 |
| 要素レンダリング遅延 | リソース到着から描画されるまでの時間 | CSS/JS によってブロックされる |
最適な LCP は 読み込み遅延をゼロ近くに保ちます——ブラウザは LCP リソースをほぼ即座に発見してフェッチを開始すべきです。
1. TTFB を削る。 これはサーバーサイドの話です:キャッシュ、CDN エッジ配信、そしてレンダリングをブロックするオリジン側の処理を避けること。CDN から配信される静的サイトなら、TTFB は 2 桁ミリ秒であるべきです。もし数百ミリ秒なら、画像に手をつける前にキャッシュかオリジンの問題があります。
2. LCP リソースをプリロードする。 典型的な読み込み遅延のバグ:LCP 画像が CSS で参照されているか JS によって注入されているため、ブラウザがそれを発見するのが遅れます。初期 HTML の中で発見可能にし、優先度をヒントとして与えましょう:
<!-- Preload + high priority so the browser fetches it immediately -->
<link rel="preload" as="image" href="/hero.avif" fetchpriority="high" />
<!-- Or directly on the img — fetchpriority avoids a separate preload -->
<img src="/hero.avif" fetchpriority="high" alt="Product hero" width="1280" height="720" />
3. 画像そのものを最適化する。 モダンなフォーマット(AVIF/WebP)を配信し、実際にレンダリングされる寸法にサイズを合わせ、レスポンシブな srcset を使いましょう。LCP 画像を遅延読み込みしてはいけません——ヒーローに対する loading="lazy" は自ら招いた LCP の傷です。最も重要な描画を後回しにしてしまうからです。
<img
src="/hero-800.avif"
srcset="/hero-400.avif 400w, /hero-800.avif 800w, /hero-1600.avif 1600w"
sizes="(max-width: 600px) 100vw, 800px"
fetchpriority="high"
alt="Product hero"
width="800" height="450" />
4. フォントを扱う。 LCP 要素がテキスト(大きな見出し)の場合、ブロッキングする Web フォントがレンダリングを遅延させます。フォントをプリロードし、font-display: swap(または optional)を使い、サードパーティへの接続を省くためにセルフホストしましょう。
@font-face {
font-family: "Inter";
src: url("/fonts/inter.woff2") format("woff2");
font-display: swap; /* render text immediately, swap when font loads */
}
💡 ヒント:最適化する前に、どの 要素が LCP なのかを特定しましょう。DevTools では Performance パネルがそれを明示的にマークし、PageSpeed Insights は名前を挙げてくれます。間違った要素を最適化することは、パフォーマンス作業で最もよくある無駄な午後です。
INP を改善する
INP はメインスレッドの問題です。ユーザーがクリックすると、ブラウザはイベントハンドラを実行し、スタイルとレイアウトを再計算してから、次のフレームを描画する必要があります。メインスレッドがビジー状態——長いタスクを実行中——なら、それらは何も起きず、インタラクションが固まったように感じられます。
最もレバレッジの効く一手は 長いタスクを分割すること です(50 ms を超えるものはすべてメインスレッドをブロックします)。大雑把なものから外科的なものまで、3 つのテクニックがあります。
1. メインスレッドに譲る(yield)。 作業のチャンクの合間に、ブラウザに保留中の入力を処理させます。モダンなプリミティブは scheduler.yield() で、setTimeout(0) はフォールバックです。
async function processLargeList(items) {
for (const item of items) {
doExpensiveWork(item);
// Yield so a pending click can be processed mid-loop
if (navigator.scheduling?.isInputPending?.()) {
await scheduler.yield(); // falls back to setTimeout in older browsers
}
}
}
2. 視覚的な応答を重い処理から切り離す。 ユーザーが期待するフィードバックを 先に 描画し、それから高コストな計算を後回しにすることで、次の描画をブロックしないようにします:
button.addEventListener("click", () => {
// 1. Immediate visual feedback — runs before the next paint
button.classList.add("is-loading");
// 2. Defer the heavy work past the paint
requestAnimationFrame(() => {
setTimeout(() => runExpensiveUpdate(), 0);
});
});
3. 不要な再レンダリングを避ける。 コンポーネントフレームワークでは、1 回のクリックが再レンダリングのカスケードを引き起こすのは典型的な INP キラーです。メモ化し、高頻度のハンドラをデバウンスし、状態更新のスコープを絞りましょう:
// React: debounce a search-as-you-type handler so each keystroke
// doesn't trigger a full filter + re-render on the critical path
const onChange = useMemo(
() => debounce((q) => setQuery(q), 150),
[]
);
その他の確実な成果:CPU を多用する処理(パース、画像処理)を Web Worker に移す、メインスレッドを占有するサードパーティスクリプトを削る、そしてハンドラ内での同期的なレイアウト読み取りを避ける(offsetHeight を読んでからスタイルを書き込むと「レイアウトスラッシング」を強制してしまいます)。
🧑💻 開発者視点:INP は平均ではなく 最悪 のインタラクションを測ります。それ以外はキビキビ動くページでも、1 回の遅いモーダルオープンがフィールドスコアを台無しにすることがあります。ページ読み込みだけでなく、ユーザーが実際に最も頻繁に行うインタラクション——メニューの開閉、検索、カート追加——をプロファイルしましょう。
CLS を改善する
CLS はほぼ常に、レイアウト後に読み込まれて既存のコンテンツを押しのけるコンテンツによって引き起こされます。修正は 事前にスペースを確保すること に尽きます。
1. 画像と動画には必ず寸法を設定する。 width/height 属性(または CSS の aspect-ratio)があれば、画像が到着する前にブラウザがボックスを確保できます:
<img src="/photo.avif" width="800" height="450" alt="..." />
.media { aspect-ratio: 16 / 9; width: 100%; }
2. 広告・埋め込み・iframe 用のスペースを確保する。 動的に注入される広告スロットは、コンテンツサイトにおける CLS の発生源 No.1 です。コンテナに、最も一般的なスロットサイズに合った固定の min-height を与え、広告が埋まったときにページが飛び跳ねないようにしましょう。
.ad-slot { min-height: 280px; } /* reserve before the ad loads */
3. 既存コンテンツの上に決してコンテンツを挿入しない。 バナー、Cookie 通知、「新しいメッセージがあります」バーなど、ページを下に押し下げるものが最悪の犯人です。フローに挿入する代わりにオーバーレイ(fixed/absolute ポジショニング)するか、最初からスペースを確保しましょう。
4. フォントの差し替えを飼いならす。 フォントの差し替えはテキストメトリクスを変え、その下のすべてをシフトさせかねません。font-display: swap を、フォールバックの @font-face 上の size-adjust・ascent-override・descent-override ディスクリプタと組み合わせて、フォールバックと Web フォントが同じスペースを占めるようにしましょう:
@font-face {
font-family: "Inter-fallback";
src: local("Arial");
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
}
⚠️ 注意:CLS は 予期しない シフトのみをカウントします。ユーザーのインタラクションから 500 ms 以内のシフト(例:アコーディオンの展開)は除外されます。だからすべてのアニメーションを恐れる必要はありません——ユーザーが求めていない動きだけを恐れればよいのです。
デバッグワークフロー
ここでは繰り返し使えるループを紹介します。安価で広範なものから、深くて具体的なものへと進みます。
ステップ 1 — フィールドデータでトリアージする。 PageSpeed Insights(または Search Console の Core Web Vitals レポート)を開き、まず フィールド セクションを読みます。これにより、75 パーセンタイルで実際にどの指標が失敗しているか、そしてどのデバイスクラスか(通常はモバイルが最初に負けます)がわかります。壊れていないものを最適化してはいけません。
ステップ 2 — ラボで再現する。 Lighthouse(DevTools → Lighthouse、モバイル+スロットリングをオン)を実行して、具体的な診断(「Largest Contentful Paint element」「Avoid large layout shifts」「Reduce unused JavaScript」)を伴う、決定論的でデバッグ可能な実行を得ます。
ステップ 3 — DevTools で特定の指標をプロファイルする。
- LCP/CLS の場合:Performance パネルがトレースを記録します。LCP 要素をマークし、すべてのレイアウトシフトを赤い「Layout Shift」トラックで示します——いずれかをクリックすると、正確にどのノードが動いたかがわかります。
- INP の場合:interaction トラックを有効にし、ページ上をクリックして回り、最も長いインタラクションを調べます。DevTools はそれを input delay、processing time、presentation delay に分解するので、問題がビジーなメインスレッド(input delay)にあるのか、遅いハンドラ(processing)にあるのかがわかります。
ステップ 4 — web-vitals ライブラリで計装する。 ラボデータでは実際の INP を捉えられません。公式ライブラリを組み込んで、自分自身のユーザーからフィールド指標を収集し、アナリティクスへビーコンで送信しましょう:
import { onLCP, onINP, onCLS } from "web-vitals";
function send(metric) {
navigator.sendBeacon(
"/analytics",
JSON.stringify({ name: metric.name, value: metric.value, id: metric.id })
);
}
onLCP(send);
onINP(send);
onCLS(send);
各指標オブジェクトには attribution ビルド(web-vitals/attribution)が付属し、問題のある要素や最大のシフト源を教えてくれます——つまり RUM データが単なる数値ではなく、修正箇所をまっすぐ指し示してくれます。
ステップ 5 — フィールドで検証する。 リリース後は待ちます。CrUX はローリングの 28 日間ウィンドウを使うため、フィールドの改善が完全に反映されるまでには数週間かかります。自分自身の web-vitals ビーコンは数日以内に変化を示します——速いフィードバックにはそれを使い、ウィンドウが追いついたら PageSpeed Insights と照合しましょう。
どこにつながるか
Core Web Vitals は、サイトを構築するときに下す意思決定の下流にあります——レンダリング戦略、アセットパイプライン、フォント読み込み、ホスティングのすべてが、これらの数値が どこまで 良くなれるかの上限を決めます。後から最適化するのは、最初から組み込むよりも難しいのです。
基礎——最初から速くてクロールしやすいサイトをどう構成するか——については、Site Build レイヤー に進んでください。そのレイヤーでは、これらのしきい値の達成を戦いではなくデフォルトにするアーキテクチャ上の選択(静的レンダリング、画像パイプライン、CDN 配信)を扱います。
重要なポイント
- ✅ 3 つの指標、3 つの問い:LCP(もう表示された?)、INP(反応した?)、CLS(動いた?)。目標:75 パーセンタイルで ≤ 2.5 秒、≤ 200 ms、≤ 0.1。
- ✅ INP は 2024 年に FID に取って代わった——最初の入力だけでなく、セッション全体にわたるインタラクションのレイテンシ全体を測る。
- ✅ ラボでデバッグし、フィールドで判断する。 緑の Lighthouse スコアは、CrUX の合格スコアではない。
- ✅ LCP は分解して直す:TTFB を削り、
preload+fetchpriorityで読み込み遅延を潰し、ヒーローを決して遅延読み込みしない。 - ✅ INP は長いタスクを分割して直す:
scheduler.yield()で yield し、フィードバックを先に描画し、不要な再レンダリングを減らす。 - ✅ CLS は事前にスペースを確保して直す:画像の寸法、広告スロットの
min-height、バナーは挿入せずオーバーレイ、そしてメトリクスを合わせたフォールバックフォント。