ストーリー
高橋アーキテクトが続けた。
Core Web Vitals とは
Googleが定義した、ユーザー体験の品質を測定する3つの指標です。
1. LCP(Largest Contentful Paint)
ページの最も大きなコンテンツ要素が表示されるまでの時間です。
// LCPの対象となる要素
const lcpTargetElements = [
'<img> 要素',
'<video> のポスター画像',
'CSSの background-image',
'テキストブロック(<p>, <h1> など)',
];
// LCPの目標値
const lcpThresholds = {
good: 2500, // 2.5秒以下: 良好
needsImprovement: 4000, // 4秒以下: 改善が必要
poor: Infinity, // 4秒超: 不良
};
改善方法:
// 1. 画像の遅延読み込みとpreload
// Hero画像は preload で先読み
// <link rel="preload" as="image" href="/hero.webp">
// 2. サーバーサイドレンダリング(SSR)でTTFBを短縮
// 3. CDNで静的アセットを配信
// 4. レンダリングブロックリソースを最小化
2. INP(Interaction to Next Paint)
ユーザーの操作(クリック、タップ、キー入力)からUIが更新されるまでの時間です。FIDの後継指標です。
// INPの目標値
const inpThresholds = {
good: 200, // 200ms以下: 良好
needsImprovement: 500, // 500ms以下: 改善が必要
poor: Infinity, // 500ms超: 不良
};
// INPが悪化する原因
const inpBottlenecks = [
'メインスレッドを長時間占有するJavaScript',
'大量のDOM操作',
'同期的なレイアウト計算(Forced Reflow)',
'重いイベントハンドラー',
];
// 改善例
class UIOptimizer {
// 重い処理をチャンクに分割
async processLargeList(items: Item[]): Promise<void> {
const CHUNK_SIZE = 50;
for (let i = 0; i < items.length; i += CHUNK_SIZE) {
const chunk = items.slice(i, i + CHUNK_SIZE);
this.renderChunk(chunk);
// ブラウザに制御を返す(UIの応答性を維持)
await new Promise(resolve => requestAnimationFrame(resolve));
}
}
// Web Workerで重い計算をオフロード
calculateInWorker(data: Data): Promise<Result> {
return new Promise((resolve) => {
const worker = new Worker('/workers/calculator.js');
worker.postMessage(data);
worker.onmessage = (e) => resolve(e.data);
});
}
}
3. CLS(Cumulative Layout Shift)
ページ読み込み中のレイアウトのずれの累積値です。
// CLSの目標値
const clsThresholds = {
good: 0.1, // 0.1以下: 良好
needsImprovement: 0.25, // 0.25以下: 改善が必要
poor: Infinity, // 0.25超: 不良
};
// CLSが発生する原因と対策
const clsFixStrategies = {
// 原因1: 画像のサイズ未指定
images: {
problem: '画像読み込み後にレイアウトがずれる',
fix: '<img width="800" height="600"> のように明示的にサイズ指定',
css: 'aspect-ratio: 16/9; で比率を維持',
},
// 原因2: 動的に挿入されるコンテンツ
dynamicContent: {
problem: '広告やバナーが後から挿入される',
fix: '事前にスペースを確保しておく(min-height指定)',
},
// 原因3: Webフォントの読み込み
fonts: {
problem: 'フォント切り替え時にテキストサイズが変わる',
fix: 'font-display: swap; + size-adjust で差異を最小化',
},
};
計測方法
Lighthouse
// Lighthouse CI の設定例
const lighthouseConfig = {
ci: {
collect: {
url: ['https://speedshop.example.com/'],
numberOfRuns: 3,
},
assert: {
assertions: {
'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
'interactive': ['error', { maxNumericValue: 3500 }],
'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
},
},
},
};
Web Vitals API
// フロントエンドで実際のユーザーデータを計測
import { onLCP, onINP, onCLS } from 'web-vitals';
function sendToAnalytics(metric: Metric): void {
fetch('/api/analytics/web-vitals', {
method: 'POST',
body: JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating, // 'good' | 'needs-improvement' | 'poor'
page: window.location.pathname,
}),
});
}
onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);
指標の優先順位
| 優先度 | 指標 | 理由 |
|---|---|---|
| 最優先 | LCP | 最初の視覚的な体験を決定 |
| 高 | INP | 操作への応答性はUXの核心 |
| 中 | CLS | レイアウトのずれは誤操作を招く |
まとめ
| ポイント | 内容 |
|---|---|
| LCP | 最大コンテンツの表示時間。2.5秒以下が目標 |
| INP | 操作への応答時間。200ms以下が目標 |
| CLS | レイアウトのずれ。0.1以下が目標 |
| 計測 | Lighthouse + Web Vitals API でラボ/フィールド両方計測 |
| SEO | Core Web VitalsはGoogleの検索ランキング要因 |
チェックリスト
- LCP、INP、CLSの意味と目標値を説明できる
- 各指標の主な悪化原因を知った
- Lighthouseでの計測方法を理解した
- Web Vitals APIでの実ユーザー計測を把握した
次のステップへ
次は「バンドル最適化」を学びます。JavaScriptのバンドルサイズを削減し、LCPとINPを改善する技法です。
推定読了時間: 30分