Core Web Vitals とは?
Core Web Vitalsは、Googleが定義したWebページのユーザー体験を測定する3つの指標です。SEOのランキング要因にもなっています。
3つの指標
| 指標 | 正式名称 | 測定内容 | 良好 | 要改善 |
|---|---|---|---|---|
| LCP | Largest Contentful Paint | 最大コンテンツの表示速度 | 2.5秒以下 | 4秒以上 |
| INP | Interaction to Next Paint | インタラクションの応答性 | 200ms以下 | 500ms以上 |
| CLS | Cumulative Layout Shift | レイアウトのずれ | 0.1以下 | 0.25以上 |
LCP(最大コンテンツの表示速度)の改善
LCPとは
ページ内で最も大きなコンテンツ(画像、テキストブロック等)が表示されるまでの時間です。
改善方法
1. 画像の最適化
<!-- NG: 巨大な画像をそのまま -->
<img src="hero.jpg" alt="Hero">
<!-- OK: 最適化された画像 -->
<img
src="hero.webp"
srcset="hero-400.webp 400w, hero-800.webp 800w, hero-1200.webp 1200w"
sizes="(max-width: 768px) 100vw, 1200px"
alt="Hero"
width="1200"
height="630"
loading="eager"
fetchpriority="high"
decoding="async"
>
ポイント:
- WebP/AVIF フォーマットを使用(JPEGより30-50%小さい)
srcsetでレスポンシブ画像を提供- LCP要素には
fetchpriority="high"とloading="eager"を設定 widthとheightを必ず指定(CLSも防げる)
2. クリティカルCSSのインライン化
<head>
<!-- ファーストビューに必要なCSSをインラインで -->
<style>
/* クリティカルCSS(ファーストビューの表示に必要な最小限のCSS) */
body { margin: 0; font-family: sans-serif; }
.hero { width: 100%; height: 60vh; }
.hero h1 { font-size: 2rem; }
</style>
<!-- 残りのCSSは非同期で読み込み -->
<link rel="preload" href="/styles/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
</head>
3. フォントの最適化
<!-- プリロードで早期取得 -->
<link rel="preload" href="/fonts/main.woff2" as="font" type="font/woff2" crossorigin>
<style>
@font-face {
font-family: 'MainFont';
src: url('/fonts/main.woff2') format('woff2');
font-display: swap; /* フォントが読み込まれるまでシステムフォントを表示 */
}
</style>
4. サーバーレスポンスの高速化
TTFB(Time to First Byte)を改善:
- CDNを使う
- サーバーサイドキャッシュ
- データベースクエリの最適化
- HTTP/2 or HTTP/3を有効にする
INP(インタラクションの応答性)の改善
INPとは
ユーザーの操作(クリック、タップ、キー入力)から画面が更新されるまでの時間です。
改善方法
1. 長いタスクを分割する
// NG: 長い処理がメインスレッドをブロック
function processLargeData(items) {
items.forEach(item => {
heavyComputation(item); // 各アイテムに時間がかかる
});
}
// OK: requestIdleCallback で分割
function processLargeDataInChunks(items) {
const iterator = items[Symbol.iterator]();
function processChunk(deadline) {
while (deadline.timeRemaining() > 0) {
const { value, done } = iterator.next();
if (done) return;
heavyComputation(value);
}
requestIdleCallback(processChunk);
}
requestIdleCallback(processChunk);
}
// OK: scheduler.yield() を使う(新しいAPI)
async function processWithYield(items) {
for (const item of items) {
heavyComputation(item);
// 定期的にメインスレッドに制御を返す
if (navigator.scheduling?.isInputPending()) {
await scheduler.yield();
}
}
}
2. イベントハンドラの最適化
// NG: 重い処理をイベントハンドラ内で実行
button.addEventListener('click', () => {
// 重い計算...
const result = heavyComputation();
updateUI(result);
});
// OK: UIの更新を先に、重い処理は後で
button.addEventListener('click', () => {
// まずUIを更新(ローディング表示等)
showLoading();
// 重い処理は次のフレームで
requestAnimationFrame(() => {
setTimeout(() => {
const result = heavyComputation();
updateUI(result);
hideLoading();
}, 0);
});
});
3. Web Worker で重い処理をオフロード
// worker.js
self.addEventListener('message', (e) => {
const result = heavyComputation(e.data);
self.postMessage(result);
});
// main.js
const worker = new Worker('/worker.js');
worker.postMessage(data);
worker.addEventListener('message', (e) => {
updateUI(e.result);
});
CLS(レイアウトのずれ)の改善
CLSとは
ページの読み込み中にレイアウトが予期せずずれる度合いです。
改善方法
1. 画像・動画にサイズを指定
<!-- NG: サイズ未指定 → 読み込み完了時にレイアウトがずれる -->
<img src="photo.jpg" alt="Photo">
<!-- OK: width/height を指定 → ブラウザが領域を確保 -->
<img src="photo.jpg" alt="Photo" width="800" height="600">
<!-- OK: CSSでアスペクト比を指定 -->
<style>
.video-wrapper {
aspect-ratio: 16 / 9;
width: 100%;
}
</style>
<div class="video-wrapper">
<iframe src="..." width="100%" height="100%"></iframe>
</div>
2. Web フォントによるずれを防ぐ
/* font-display: swap でFOUT(Flash of Unstyled Text)を許容 */
@font-face {
font-family: 'CustomFont';
src: url('/fonts/custom.woff2') format('woff2');
font-display: swap;
}
/* size-adjust でフォールバックフォントのサイズを調整 */
@font-face {
font-family: 'CustomFont-Fallback';
src: local('Arial');
size-adjust: 105%;
ascent-override: 90%;
descent-override: 20%;
}
3. 動的コンテンツの領域を確保
/* 広告やバナーの領域を事前に確保 */
.ad-slot {
min-height: 250px; /* 広告の最小高さ */
background: #f5f5f5;
}
/* 遅延読み込みコンテンツの領域 */
.lazy-content {
min-height: 300px;
contain: layout; /* レイアウトの影響範囲を制限 */
}
4. transform アニメーションを使う
/* NG: top/left でアニメーション → レイアウトシフトが発生 */
.element {
transition: top 0.3s;
}
.element.active {
top: 100px;
}
/* OK: transform でアニメーション → レイアウトシフトなし */
.element {
transition: transform 0.3s;
}
.element.active {
transform: translateY(100px);
}
JavaScript の最適化
コード分割(Code Splitting)
// 動的インポートでルートごとに分割
const HomePage = lazy(() => import('./pages/Home'));
const AboutPage = lazy(() => import('./pages/About'));
// Vite の設定
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom'],
utils: ['lodash-es', 'date-fns'],
},
},
},
},
});
Tree Shaking
// NG: ライブラリ全体をインポート
import _ from 'lodash';
_.debounce(fn, 300);
// OK: 必要な関数だけインポート
import debounce from 'lodash-es/debounce';
debounce(fn, 300);
script タグの最適化
<!-- ブロッキングしない読み込み -->
<script src="app.js" defer></script> <!-- HTML解析後に実行 -->
<script src="analytics.js" async></script> <!-- 非同期で読み込み・実行 -->
<!-- モジュールはデフォルトでdefer -->
<script type="module" src="app.js"></script>
測定ツール
Lighthouse
# CLI で実行
npx lighthouse https://example.com --view
# Chrome DevTools
# → Lighthouse タブ → Analyze page load
Web Vitals ライブラリ
import { onLCP, onINP, onCLS } from 'web-vitals';
onLCP(metric => {
console.log('LCP:', metric.value);
// アナリティクスに送信
sendToAnalytics({ name: 'LCP', value: metric.value });
});
onINP(metric => {
console.log('INP:', metric.value);
});
onCLS(metric => {
console.log('CLS:', metric.value);
});
その他のツール
| ツール | 用途 |
|---|---|
| PageSpeed Insights | Googleのパフォーマンス分析 |
| WebPageTest | 詳細なウォーターフォール分析 |
| Chrome DevTools Performance | ランタイムパフォーマンス分析 |
| Bundle Analyzer | バンドルサイズの可視化 |
チェックリスト
| カテゴリ | チェック項目 |
|---|---|
| 画像 | WebP/AVIF使用、srcset設定、width/height指定 |
| フォント | preload、font-display: swap、サブセット化 |
| JavaScript | コード分割、Tree Shaking、defer/async |
| CSS | クリティカルCSS、未使用CSS削除 |
| サーバー | CDN、HTTP/2+、Gzip/Brotli圧縮 |
| キャッシュ | Cache-Control設定、Service Worker |
まとめ
| 指標 | 最も効果的な改善策 |
|---|---|
| LCP | 画像最適化 + fetchpriority + CDN |
| INP | 長いタスクの分割 + Web Worker |
| CLS | width/height指定 + font-display: swap |
パフォーマンス改善は一度やって終わりではなく、継続的に計測・改善するサイクルが重要です。まずはLighthouseでスコアを確認し、最もインパクトの大きい項目から着手しましょう。
画像の最適化・リサイズには、Assistyの画像リサイズツールが便利です。ブラウザ上で画像を最適なサイズに変換できます。