Tips
Webパフォーマンス最適化完全ガイド - Core Web Vitals対応
kitahara-dev•2025/10/31•14 min read
Webパフォーマンス最適化完全ガイド
Webアプリケーションのパフォーマンスは、ユーザー体験、SEO、コンバージョン率に直結する最重要要素です。このガイドでは、実践的な最適化テクニックと具体的な数値目標を紹介します。
📊 目次
- パフォーマンス測定ツール
- Core Web Vitalsの詳細
- 画像の最適化
- コード分割とLazy Loading
- キャッシュ戦略
- バンドルサイズの削減
- レンダリングパフォーマンス
- ネットワーク最適化
- フォントの最適化
- 実際の改善事例
1. パフォーマンス測定ツール
Lighthouse(推奨)
Chrome DevToolsに統合された総合パフォーマンス測定ツール。
# コマンドラインから実行
npm install -g lighthouse
lighthouse https://your-site.com --view
主要指標:
- Performance Score (0-100)
- Accessibility Score (0-100)
- Best Practices Score (0-100)
- SEO Score (0-100)
Chrome DevTools Performance Tab
リアルタイムのパフォーマンス分析:
- DevToolsを開く (F12)
- Performanceタブを選択
- 記録開始 → ページ操作 → 記録停止
- フレームレート、CPUアクティビティ、ネットワーク活動を分析
WebPageTest
実際のデバイスとネットワーク条件でテスト:
URL: https://www.webpagetest.org/
設定例:
- Location: Tokyo, Japan
- Browser: Chrome
- Connection: 4G (実際のモバイル環境)
パフォーマンス監視の設定
// app/layout.tsx - Web Vitalsの測定
export function reportWebVitals(metric: NextWebVitalsMetric) {
console.log(metric);
// Google Analyticsに送信
if (window.gtag) {
window.gtag('event', metric.name, {
value: Math.round(metric.value),
event_label: metric.id,
non_interaction: true,
});
}
}
2. Core Web Vitalsの詳細
LCP (Largest Contentful Paint)
目標: 2.5秒以下
最大コンテンツの描画時間。ユーザーが実際にコンテンツを見られるまでの時間。
改善方法:
// 1. 画像の優先読み込み
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority // LCPに影響する画像
sizes="100vw"
/>
// 2. サーバーレスポンス時間の改善(SSG/ISR)
export const revalidate = 3600; // 1時間ごとに再検証
// 3. フォントの最適化
import { Inter } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap', // フォールバックフォントを即座に表示
});
計測:
import { onLCP } from 'web-vitals';
onLCP(({ value, rating }) => {
console.log('LCP:', value, rating);
// 'good': < 2.5s, 'needs-improvement': 2.5-4s, 'poor': > 4s
});
FID (First Input Delay) → INP (Interaction to Next Paint)
目標: 100ミリ秒以下(FID) / 200ミリ秒以下(INP)
ユーザー操作への応答性。
改善方法:
// 1. 重い処理をWeb Workerで実行
// worker.ts
self.onmessage = (e) => {
const result = heavyComputation(e.data);
self.postMessage(result);
};
// component.tsx
const worker = new Worker(new URL('./worker.ts', import.meta.url));
worker.postMessage(data);
worker.onmessage = (e) => {
setResult(e.data);
};
// 2. requestIdleCallbackで非緊急処理を遅延
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
// 優先度の低い処理
trackAnalytics();
});
}
// 3. デバウンス/スロットルの活用
import { debounce } from 'lodash';
const handleSearch = debounce((query: string) => {
fetchSearchResults(query);
}, 300);
CLS (Cumulative Layout Shift)
目標: 0.1以下
視覚的な安定性。予期しないレイアウトシフトを防ぐ。
改善方法:
// 1. 画像・動画に明示的なサイズ指定
<Image
src="/image.jpg"
alt="Description"
width={800} // 必須
height={600} // 必須
/>
// 2. フォント読み込み中のレイアウトシフト防止
import { Inter } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap',
fallback: ['system-ui', 'arial'], // フォールバック指定
});
// 3. 動的コンテンツ用のスケルトンUI
{isLoading ? (
<div className="animate-pulse">
<div className="h-4 bg-gray-200 rounded w-3/4 mb-2"></div>
<div className="h-4 bg-gray-200 rounded w-1/2"></div>
</div>
) : (
<p>{content}</p>
)}
// 4. 広告スペースの確保
<div className="h-[250px] bg-gray-100">
{/* 広告が読み込まれるまでスペース確保 */}
<AdComponent />
</div>
3. 画像の最適化
適切なフォーマット選択
| フォーマット | 用途 | サイズ削減 |
|---|---|---|
| WebP | 写真・イラスト全般 | 25-35% |
| AVIF | 最新ブラウザ対応 | 50% |
| JPEG | 写真(互換性重視) | - |
| PNG | 透過性が必要な画像 | - |
| SVG | アイコン・ロゴ | 最小 |
Next.js Image最適化
// 基本的な使用
<Image
src="/photo.jpg"
alt="Photo"
width={800}
height={600}
quality={85} // デフォルト75、品質とサイズのバランス
placeholder="blur" // ぼかしプレースホルダー
blurDataURL="data:image/jpeg;base64,..."
/>
// レスポンシブ画像
<Image
src="/hero.jpg"
alt="Hero"
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
className="object-cover"
/>
// 外部画像の最適化
// next.config.js
module.exports = {
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'images.example.com',
},
],
formats: ['image/avif', 'image/webp'],
},
};
画像の遅延読み込み
<Image
src="/below-fold.jpg"
alt="Below fold image"
width={800}
height={600}
loading="lazy" // ビューポート外の画像を遅延読み込み
/>
画像圧縮ツール
# Sharp(プログラマティック)
npm install sharp
# 使用例
import sharp from 'sharp';
await sharp('input.jpg')
.resize(800, 600)
.webp({ quality: 80 })
.toFile('output.webp');
# ImageOptim(GUI)
# TinyPNG(オンライン)
# Squoosh(Webアプリ)
4. コード分割とLazy Loading
動的インポート
// コンポーネントの動的読み込み
import dynamic from 'next/dynamic';
const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
loading: () => <div className="animate-pulse">読み込み中...</div>,
ssr: false, // クライアントサイドのみで読み込み
});
// 条件付き読み込み
const AdminPanel = dynamic(() => import('@/components/AdminPanel'));
function Dashboard({ isAdmin }: { isAdmin: boolean }) {
return (
<div>
<h1>ダッシュボード</h1>
{isAdmin && <AdminPanel />}
</div>
);
}
ルートベースのコード分割
Next.js App Routerは自動的にルートごとに分割:
app/
page.tsx → home.js (小)
about/
page.tsx → about.js (小)
dashboard/
page.tsx → dashboard.js (大)
React Suspenseの活用
import { Suspense } from 'react';
export default function Page() {
return (
<div>
<h1>ページタイトル</h1>
<Suspense fallback={<Loading />}>
<HeavyComponent />
</Suspense>
</div>
);
}
5. キャッシュ戦略
HTTPキャッシュヘッダー
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/static/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
},
],
},
{
source: '/:path*.{jpg,jpeg,png,gif,webp,svg}',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=86400, stale-while-revalidate=604800',
},
],
},
];
},
};
Next.js Data Cache
// Static(デフォルト)- ビルド時にキャッシュ
export default async function Page() {
const data = await fetch('https://api.example.com/data', {
cache: 'force-cache'
});
return <div>{data.title}</div>;
}
// Revalidate - 定期的に再検証
export default async function Page() {
const data = await fetch('https://api.example.com/data', {
next: { revalidate: 3600 } // 1時間
});
return <div>{data.title}</div>;
}
// Dynamic - 常に最新データ
export default async function Page() {
const data = await fetch('https://api.example.com/data', {
cache: 'no-store'
});
return <div>{data.title}</div>;
}
Service Workerキャッシング
// public/sw.js
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('v1').then((cache) => {
return cache.addAll([
'/',
'/styles.css',
'/script.js',
]);
})
);
});
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});
6. バンドルサイズの削減
バンドル分析
# Next.js Bundle Analyzer
npm install @next/bundle-analyzer
# next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true',
});
module.exports = withBundleAnalyzer({});
# 実行
ANALYZE=true npm run build
ツリーシェイキング
// ❌ 悪い例:ライブラリ全体をインポート
import _ from 'lodash';
const result = _.debounce(fn, 300);
// ✅ 良い例:必要な関数のみインポート
import debounce from 'lodash/debounce';
const result = debounce(fn, 300);
// ✅ さらに良い:専用ライブラリ
import { debounce } from 'lodash-es'; // ES Modules版
不要な依存関係の削除
# 依存関係の分析
npm install -g depcheck
depcheck
# 未使用パッケージの削除
npm uninstall unused-package
7. レンダリングパフォーマンス
React.memoの活用
import { memo } from 'react';
const ExpensiveComponent = memo(({ data }: { data: Data }) => {
return <div>{/* 重い描画処理 */}</div>;
});
// propsが変更された時のみ再レンダリング
useMemoとuseCallback
import { useMemo, useCallback } from 'react';
function Component({ items }: { items: Item[] }) {
// 重い計算結果をメモ化
const sortedItems = useMemo(() => {
return items.sort((a, b) => a.value - b.value);
}, [items]);
// 関数をメモ化(子コンポーネントへの不要な再レンダリング防止)
const handleClick = useCallback((id: string) => {
console.log('Clicked:', id);
}, []);
return (
<div>
{sortedItems.map(item => (
<ChildComponent key={item.id} onClick={handleClick} />
))}
</div>
);
}
仮想化(Virtualization)
// react-windowを使用
import { FixedSizeList } from 'react-window';
function VirtualList({ items }: { items: Item[] }) {
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={50}
width="100%"
>
{({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
)}
</FixedSizeList>
);
}
8. ネットワーク最適化
HTTP/2とHTTP/3
# Nginxの設定例
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
# HTTP/3(QUIC)の有効化
listen 443 quic reuseport;
listen [::]:443 quic reuseport;
ssl_protocols TLSv1.2 TLSv1.3;
}
リソースヒント
// app/layout.tsx
export default function RootLayout({ children }) {
return (
<html>
<head>
{/* DNS prefetch */}
<link rel="dns-prefetch" href="https://api.example.com" />
{/* Preconnect */}
<link rel="preconnect" href="https://fonts.googleapis.com" />
{/* Prefetch */}
<link rel="prefetch" href="/next-page" />
{/* Preload */}
<link
rel="preload"
href="/fonts/inter.woff2"
as="font"
type="font/woff2"
crossOrigin="anonymous"
/>
</head>
<body>{children}</body>
</html>
);
}
CDNの活用
// next.config.js
module.exports = {
assetPrefix: process.env.NODE_ENV === 'production'
? 'https://cdn.example.com'
: '',
};
9. フォントの最適化
Next.js Font最適化
// app/layout.tsx
import { Inter, Noto_Sans_JP } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
});
const notoSansJP = Noto_Sans_JP({
weight: ['400', '700'],
subsets: ['latin'],
display: 'swap',
variable: '--font-noto-sans-jp',
});
export default function RootLayout({ children }) {
return (
<html className={`${inter.variable} ${notoSansJP.variable}`}>
<body>{children}</body>
</html>
);
}
フォントサブセット化
# pyftsubsetを使用
pip install fonttools
pyftsubset font.ttf --text-file=characters.txt --output-file=font-subset.woff2 --flavor=woff2
10. 実際の改善事例
Before → After: ECサイトの最適化
Before(改善前):
- Lighthouse Score: 45/100
- LCP: 4.2秒
- FID: 250ms
- CLS: 0.25
- バンドルサイズ: 850KB
実施した施策:
- ✅ 画像をWebPに変換(平均40%削減)
- ✅ コード分割で初期バンドルを400KBに削減
- ✅ フォントをサブセット化(日本語フォント80%削減)
- ✅ 未使用CSSを削除
- ✅ 画像にwidth/height属性追加
- ✅ 重要なリソースにpreload設定
After(改善後):
- Lighthouse Score: 92/100 (+47)
- LCP: 1.8秒 (-2.4秒)
- FID: 80ms (-170ms)
- CLS: 0.05 (-0.20)
- バンドルサイズ: 420KB (-430KB)
ビジネスインパクト:
- ページ離脱率: 35% → 18% (-17%)
- コンバージョン率: 2.1% → 3.4% (+1.3%)
- 平均セッション時間: 2:15 → 3:45 (+1:30)
📝 まとめ
Webパフォーマンス最適化のチェックリスト:
- ✅ Lighthouseで定期測定 - 目標スコア90以上
- ✅ Core Web Vitals達成 - LCP < 2.5s, FID < 100ms, CLS < 0.1
- ✅ 画像最適化 - WebP/AVIF、適切なサイズ、遅延読み込み
- ✅ コード分割 - 初期バンドル < 300KB
- ✅ キャッシュ活用 - 適切なCache-Control設定
- ✅ レンダリング最適化 - memo、useMemo、仮想化
- ✅ ネットワーク最適化 - HTTP/2、CDN、リソースヒント
- ✅ フォント最適化 - サブセット化、display: swap
パフォーマンスは継続的な改善プロセスです。定期的に測定し、ユーザー体験向上を目指しましょう!
🔗 参考リンク
#Performance#Web Optimization#Core Web Vitals#Lighthouse
著者について
kitahara-devによって執筆されました。