Next.js Core Web Vitals 最適化実戦ガイド:LCP/INP/CLS 完全攻略
先月、私はある EC サイトのパフォーマンス最適化タスクを引き受けました。Lighthouse のスコアはずっと 65点前後をうろつき、最も頭を悩ませたのは LCP(最大コンテンツ描画)が常に 3.5秒前後で変動していたことでした。オーナーは「ユーザーの離脱率が高く、コンバージョン率も上がらない」と嘆いていました。正直、かなりのプレッシャーでした。
その後、2週間かけて Core Web Vitals を体系的に最適化した結果、最終的に Lighthouse スコアは 95点に達し、LCP は 1.2秒まで短縮されました。さらに重要なことに、コンバージョン率は 28% 向上しました。この結果は、パフォーマンス最適化が単なる技術的指標ではなく、確かなビジネス価値であることを教えてくれました。
2025年の最新データによると、Google の Core Web Vitals 基準を満たしている Web サイトはわずか 47% です。パフォーマンスが悪いと、収益、ランキング、コンバージョン率において 8〜35% の損失を招きます。これは脅しではなく、現実のビジネスコストです。
この記事では、Next.js のパフォーマンス最適化プロセスで私が蓄積した実戦経験を共有します。以下のことが学べます:
- 3つのコア指標(LCP、INP、CLS)の具体的な最適化方法
- そのまま使える 10個以上のコード例
- 最も陥りやすい 5つのパフォーマンスの落とし穴
Core Web Vitals 2025年最新動向
最適化を始める前に、現在のルールを明確にしておきましょう。多くの記事ではまだ FID(初回入力遅延)について語られていますが、この指標は 2024年3月に廃止されました。
現在の3大コア指標
Google は現在、以下の3つの指標を重視しています:
-
LCP (Largest Contentful Paint) - 最大コンテンツ描画
- 目標: ≤ 2.5 秒
- 読み込み性能を測定
- Lighthouse スコアの 25% を占める
-
INP (Interaction to Next Paint) - インタラクションから次の描画まで
- 目標: ≤ 200ms
- インタラクション応答速度を測定
- 2024年3月に FID に代わって導入された新指標
-
CLS (Cumulative Layout Shift) - 累積レイアウトシフト
- 目標: < 0.1
- 視覚的な安定性を測定
FCP(初回コンテンツ描画)はコア指標ではありませんが、ユーザーの第一印象に影響するため依然として重要です。
なぜこれらの指標に注目すべきか?
こんな経験はありませんか? Web サイトを開いてボタンをクリックしようとした瞬間、ページがガクッと動いて別の場所をクリックしてしまった。これが CLS の悪さです。
あるいは、サイトにアクセスしても数秒間真っ白な画面が続き、「ネットが切れたかな?」と思ったことは? これが LCP の遅さです。
Chrome チームのデータによると、トップクラスの Web サイトの LCP 平均値は約 1,220ms です。もしあなたのサイトの LCP が 2.5秒を超えているなら、競合他社に大きく遅れをとっています。
LCP 最適化 - メインコンテンツを最速で表示する
LCP は私が最適化に最も時間を費やした指標であり、効果も最も顕著でした。具体的な最適化手順を解説します。
ステップ1: LCP 要素を特定する
最適化の前に、どれが LCP 要素かを知る必要があります。通常、LCP 要素は以下のいずれかです:
- ファーストビューのヒーロー画像
- 動画のカバー画像
- 大きな見出しやテキストブロック
素早い確認方法:
- Chrome DevTools を開く(F12)
Ctrl+Shift+P(Mac はCmd+Shift+P)を押す- “Show Rendering” と入力
- “Core Web Vitals” にチェックを入れる
- ページをリロードすると、右上に LCP 要素が表示されます
私の場合、あの EC サイトの LCP 要素はトップページのメインビジュアル画像(2MBの JPG)でした。問題はここにあったのです。
画像最適化: next/image の正しい使い方
Next.js の Image コンポーネントを使えば万事解決だと思っている人が多いですが、そうではありません。私も最初はそう思っていましたが、LCP は遅いままでした。
優先度設定こそが鍵
LCP 画像については、ブラウザに「この画像は重要だから優先的に読み込め!」と明確に伝える必要があります。Next.js には2つの方法があります:
Next.js 13-15(主流バージョン):
import Image from 'next/image';
export default function Hero() {
return (
<Image
src="/hero-image.jpg"
width={1200}
height={630}
priority // 重要!優先読み込みを指示
fetchPriority="high" // 二重の保険
alt="製品メインビジュアル"
/>
);
}
Next.js 16+(最新バージョン):
Next.js 16 では priority 属性が廃止され、以下の書き方に変わりました:
<Image
src="/hero-image.jpg"
width={1200}
height={630}
loading="eager" // 遅延読み込みせず、即時読み込み
fetchPriority="high" // 高優先度
alt="製品メインビジュアル"
/>
なぜ2つの属性を設定するのか? priority はプリロードタグを自動追加し、fetchPriority はリソースの重要度をブラウザに伝えます。両方を組み合わせるのがベストです。
間違った書き方と比較:
// ❌ 間違い:遅延読み込みされ、LCP が遅くなる
<Image
src="/hero-image.jpg"
width={1200}
height={630}
alt="製品メインビジュアル"
/>
// ❌ 間違い:サイズ情報が欠けている
<Image
src="/hero-image.jpg"
priority
alt="製品メインビジュアル"
/>
サイズ指定の重要性
Next.js の Image コンポーネントは width と height の指定を求めます。これは意地悪ではなく、CLS(レイアウトシフト)を防ぐためです。
レスポンシブレイアウトの場合は以下のように書きます:
// fill 属性でレスポンシブ化
<div style={{ position: 'relative', width: '100%', aspectRatio: '16/9' }}>
<Image
src="/hero-image.jpg"
fill
priority
style={{ objectFit: 'cover' }}
alt="製品メインビジュアル"
/>
</div>
親コンテナに明確なサイズか aspect-ratio がないと、画像はどのサイズで描画すべきか分からなくなるので注意してください。
フォント最適化: next/font の自動インライン化
フォントの読み込み遅延も LCP を悪化させます。特に日本語フォントは数 MB になることもあります。Next.js 13 で導入された next/font は、フォント読み込みを自動的に最適化します。
Google Fonts の使用:
// app/layout.jsx
import { Inter, Noto_Sans_JP } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap', // フォントのちらつき防止
});
const notoSansJP = Noto_Sans_JP({
subsets: ['latin'], // 日本語サブセットがない場合もあるので注意
weight: ['400', '700'],
preload: false, // 日本語フォントは重いので preload を false にすることもある
display: 'swap',
});
export default function RootLayout({ children }) {
return (
<html lang="ja" className={`${inter.className} ${notoSansJP.className}`}>
<body>{children}</body>
</html>
);
}
カスタムフォントの使用:
import localFont from 'next/font/local';
const myFont = localFont({
src: './my-font.woff2',
display: 'swap',
});
export default function Layout({ children }) {
return <div className={myFont.className}>{children}</div>;
}
next/font は以下の最適化を自動で行います:
- フォント CSS の自動インライン化(ネットワークリクエスト削減)
- 自動サブセット化(必要な文字だけ読み込み)
- フォントファイルの自動プリロード
- レイアウトシフトの排除
プロジェクトのフォントを next/font に切り替えただけで、LCP がさらに 0.3秒短縮されました。
サーバー応答時間の短縮
画像もフォントも最適化したのに LCP が遅い? サーバー応答時間(TTFB)が原因かもしれません。
静的生成を優先する
Next.js は複数のレンダリング方式を提供していますが、パフォーマンス順に並べると以下の通りです:
- SSG (Static Site Generation) - ビルド時に HTML 生成、最速
- ISR (Incremental Static Regeneration) - オンデマンドで静的ページ再生成
- SSR (Server-Side Rendering) - リクエストごとに HTML 生成、比較的遅い
- CSR (Client-Side Rendering) - クライアントレンダリング、LCP は最も遅い
可能な限り SSG を使いましょう:
// app/blog/[slug]/page.jsx
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({
slug: post.slug,
}));
}
export default async function BlogPost({ params }) {
const post = await getPost(params.slug);
return <article>{/* ... */}</article>;
}
データ更新が必要なら ISR を使います:
// app/products/[id]/page.jsx
export const revalidate = 3600; // 1時間ごとに再生成
export default async function ProductPage({ params }) {
const product = await getProduct(params.id);
return <div>{/* ... */}</div>;
}
CDN と Edge Network の活用
Vercel にデプロイしている場合、静的リソースは自動的に世界中の Edge Network に配信され、TTFB が大幅に短縮されます。
他のプラットフォームを使用する場合は、Cloudflare や AWS CloudFront などの CDN と組み合わせることをお勧めします。
サーバーでの複雑な計算を避ける
私もかつて、サーバー側で複雑なデータ処理を行い、レンダリングのたびに 500ms かかって LCP を悪化させるというミスを犯しました。
悪い例:
// ❌ 間違い:リクエストごとに計算
export default async function Page() {
const data = await fetchData();
const processed = heavyProcessing(data); // 500ms 消費
return <div>{processed}</div>;
}
正しい例:
計算結果をキャッシュするか、ビルド時に計算しておきます:
// ✅ 正解:ビルド時に計算
export async function generateStaticParams() {
const data = await fetchData();
const processed = heavyProcessing(data);
// 結果を DB かファイルに保存
await saveProcessedData(processed);
}
export default async function Page() {
const processed = await getProcessedData(); // 直接読み込み
return <div>{processed}</div>;
}
CLS 最適化 - レイアウトのズレをなくす
CLS(累積レイアウトシフト)は最も厄介な指標です。ページが突然動くとユーザー体験は最悪です。正直、この問題には私もかなり悩まされました。
画像とメディア要素の場所取り
CLS の最も一般的な原因は、画像が読み込まれた後にレイアウトが変わることです。解決策は簡単、ブラウザに画像のサイズを事前に教えることです。
Next.js Image コンポーネントは自動処理します:
// ✅ 正解:幅と高さを指定、CLS は発生しない
<Image
src="/product.jpg"
width={400}
height={300}
alt="製品画像"
/>
動的に読み込まれる画像の場合は?
CMS から画像を取得し、サイズが不明な場合:
// ✅ 正解:aspect-ratio でスペースを確保
<div style={{ position: 'relative', width: '100%', aspectRatio: '4/3' }}>
<Image
src={dynamicImageUrl}
fill
style={{ objectFit: 'cover' }}
alt="動的画像"
/>
</div>
動画も同様です:
<video
width="1280"
height="720"
poster="/video-poster.jpg"
controls
>
<source src="/video.mp4" type="video/mp4" />
</video>
動的コンテンツの場所取り
広告、通知バー、埋め込みコンテンツ(Twitter カードなど)も CLS の原因になります。これらに対しても事前にスペースを確保すべきです。
広告コンテナ:
// ✅ 正解:広告の高さを確保
<div
style={{
minHeight: '250px', // Google AdSense 標準広告高さ
backgroundColor: '#f0f0f0' // プレースホルダー背景色
}}
>
{/* 広告スクリプト */}
<ins className="adsbygoogle" />
</div>
通知バー:
上部の通知バーを突然出現させてはいけません:
// ❌ 間違い:突然出現し、ページ全体を押し下げる
{showBanner && <NotificationBanner />}
// ✅ 正解:事前にスペースを確保
<div style={{ minHeight: '60px' }}>
{showBanner ? <NotificationBanner /> : <div style={{ height: '60px' }} />}
</div>
スケルトン(Skeleton):
動的読み込みコンテンツにはスケルトンスクリーンを使用:
function ProductList() {
const { data, isLoading } = useQuery('products', fetchProducts);
if (isLoading) {
return (
<div className="grid grid-cols-3 gap-4">
{Array.from({ length: 6 }).map((_, i) => (
<div key={i} className="skeleton" style={{ height: '300px' }} />
))}
</div>
);
}
return (
<div className="grid grid-cols-3 gap-4">
{data.map(product => <ProductCard key={product.id} {...product} />)}
</div>
);
}
フォント読み込みの最適化
フォント読み込みも「テキストのちらつき」や「フォントの入れ替わり」による CLS を引き起こします。
next/font は自動処理しますが、手動設定も可能です:
const inter = Inter({
subsets: ['latin'],
display: 'optional', // 読み込みが遅ければシステムフォントを使用し、切り替えない
adjustFontFallback: true, // 代替フォントのサイズを自動調整し、ズレを軽減
});
display 属性の選択肢:
swap: 代替フォントを先に表示し、読み込み完了後に切り替える(CLS のリスクあり)optional: 読み込みが間に合わなければ代替フォントを使い続け、切り替えない(推奨)block: 読み込み完了まで文字を隠す(非推奨)fallback: swap と optional の中間
私は optional をお勧めします。カスタムフォントが表示されない可能性はありますが、UX はその方が優れています。
JavaScript 駆動のレスポンシブレイアウトを避ける
これは 2025年 最もよくある CLS の罠です! 多くの Next.js プロジェクトがこのミスを犯しています。
典型的な間違い:
// ❌ 間違い:深刻な CLS を引き起こす
import { useMediaQuery } from '@/hooks/useMediaQuery';
export default function ResponsiveLayout() {
const isMobile = useMediaQuery('(max-width: 768px)');
return (
<div>
{isMobile ? (
<MobileNav />
) : (
<DesktopNav />
)}
</div>
);
}
なぜ CLS が起きるのか?
- 初回レンダリング時、JS 未実行のため
isMobileはundefinedかデフォルト値 - JS 実行後、
useMediaQueryが実際の値を返す - コンポーネントが再レンダリングされ、レイアウトが急変する
サーバーサイドレンダリング(SSR)ではサーバーが画面幅を知り得ないため、この問題はさらに深刻です。
正しい方法:CSS メディアクエリを使う
// ✅ 正解:CSS で表示/非表示を制御、CLS は発生しない
export default function ResponsiveLayout() {
return (
<>
<nav className="mobile-nav md:hidden">
<MobileNav />
</nav>
<nav className="desktop-nav hidden md:block">
<DesktopNav />
</nav>
</>
);
}
どうしても JS で判断が必要な場合(デバイスに応じたデータ取得など)、サーバーサイドで User-Agent を取得します:
// app/page.jsx
import { headers } from 'next/headers';
export default async function Page() {
const headersList = headers();
const userAgent = headersList.get('user-agent') || '';
const isMobile = /mobile/i.test(userAgent);
return (
<div>
{isMobile ? <MobileView /> : <DesktopView />}
</div>
);
}
これでサーバーとクライアントのレンダリング結果が一致し、CLS は発生しません。
FCP 最適化 - 初回描画を加速する
FCP(初回コンテンツ描画)はコア指標ではありませんが、第一印象を左右します。白屏時間が長すぎると、ユーザーは即座に離脱します。
重要リソースの最適化
FCP が遅い主な原因は、レンダリングをブロックするリソースです。Chrome DevTools の Coverage パネルで、未使用の CSS や JS を確認しましょう。
重要 CSS のインライン化:
Next.js は CSS を自動最適化しますが、手動で重要スタイルをインライン化することもできます:
// app/layout.jsx
export default function RootLayout({ children }) {
return (
<html>
<head>
<style
dangerouslySetInnerHTML={{
__html: `
/* 重要 CSS:ファーストビューに必要なスタイル */
body { margin: 0; font-family: system-ui; }
.hero { height: 100vh; }
`,
}}
/>
</head>
<body>{children}</body>
</html>
);
}
非重要 CSS の遅延ロード:
首屏に不要なスタイル(モーダル、折りたたみ領域など)は遅延ロードします:
// コンポーネント内で動的インポート
import dynamic from 'next/dynamic';
const Modal = dynamic(() => import('./Modal'), {
loading: () => <p>Loading...</p>,
});
サードパーティスクリプトの管理
サードパーティスクリプト(Google Analytics、広告、SNS プラグイン)はパフォーマンスキラーです。多くのサイトの FCP が遅い原因はこれです。
next/script の活用:
Next.js の Script コンポーネントで読み込みタイミングを制御します:
import Script from 'next/script';
export default function Layout({ children }) {
return (
<>
{children}
{/* Google Analytics:ページインタラクティブ後に読み込み */}
<Script
src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
strategy="lazyOnload"
/>
<Script id="ga-init" strategy="lazyOnload">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'GA_MEASUREMENT_ID');
`}
</Script>
{/* Facebook Pixel:遅延読み込み */}
<Script
src="https://connect.facebook.net/en_US/fbevents.js"
strategy="lazyOnload"
/>
</>
);
}
strategy 属性の説明:
beforeInteractive: ページインタラクティブ前に読み込み(重要スクリプト用)afterInteractive: ページインタラクティブ後に読み込み(デフォルト)lazyOnload: アイドル時に読み込み(分析や広告に推奨)worker: Web Worker で実行(実験的機能)
不要なサードパーティスクリプトをすべて lazyOnload に変更した結果、FCP は 0.8秒向上しました。
コード分割と遅延ロード
Next.js は自動でコード分割を行いますが、手動での最適化が必要な場合もあります。
コンポーネントの遅延ロード:
首屏に不要なコンポーネントは動的にインポートします:
import dynamic from 'next/dynamic';
// コメントコンポーネントを遅延ロード
const Comments = dynamic(() => import('./Comments'), {
loading: () => <div>コメント読み込み中...</div>,
ssr: false, // サーバーサイドレンダリングしない
});
export default function BlogPost({ post }) {
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
{/* ここまでスクロールして初めてコメントをロード */}
<Comments postId={post.id} />
</article>
);
}
サードパーティライブラリの遅延ロード:
重いライブラリ(チャート、リッチテキストエディタなど)はオンデマンドでロードすべきです。
ケーススタディ: チャートライブラリ遅延ロードで 800KB 削減
以前、Chart.js を全ページで読み込んでいたプロジェクトがありましたが、実際に使っていたのはダッシュボードのみでした。800KB の無駄です。
最適化前:
// ❌ 間違い:グローバルインポート、全ページでロードされる
import { Chart } from 'chart.js';
export default function Dashboard() {
return <canvas ref={chartRef} />;
}
最適化後:
// ✅ 正解:必要な時だけロード
import dynamic from 'next/dynamic';
const ChartComponent = dynamic(() => import('./ChartComponent'), {
loading: () => <div>チャート読み込み中...</div>,
ssr: false,
});
export default function Dashboard() {
return <ChartComponent />;
}
// ChartComponent.jsx
import { Chart } from 'chart.js';
export default function ChartComponent() {
return <canvas ref={chartRef} />;
}
これで、ダッシュボードにアクセスしたユーザーだけが Chart.js をダウンロードするようになります。
画像の遅延ロード:
LCP 画像以外の画像は遅延ロードすべきです:
<Image
src="/feature-image.jpg"
width={600}
height={400}
loading="lazy" // デフォルト値、省略可能
alt="機能紹介"
/>
監視と継続的な最適化
最適化は一度で終わりではありません。継続的なプロセスです。
Lighthouse CI による自動テスト
手動で Lighthouse を走らせるのは手間ですし、忘れがちです。CI/CD パイプラインに統合するのがベストです。
Lighthouse CI のインストール:
npm install -D @lhci/cli
設定ファイル lighthouserc.js:
module.exports = {
ci: {
collect: {
url: ['http://localhost:3000/', 'http://localhost:3000/products'],
numberOfRuns: 3, // 3回実行して平均を取る
},
assert: {
assertions: {
'categories:performance': ['error', { minScore: 0.9 }], // Performance ≥ 90
'first-contentful-paint': ['error', { maxNumericValue: 2000 }], // FCP ≤ 2s
'largest-contentful-paint': ['error', { maxNumericValue: 2500 }], // LCP ≤ 2.5s
'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }], // CLS < 0.1
},
},
upload: {
target: 'temporary-public-storage',
},
},
};
GitHub Actions 設定:
# .github/workflows/lighthouse.yml
name: Lighthouse CI
on: [push]
jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm install
- run: npm run build
- run: npm run start &
- run: npx @lhci/cli autorun
これでコードをプッシュするたびに Lighthouse テストが自動実行され、パフォーマンスが低下すれば CI が失敗して知らせてくれます。
Real User Monitoring (RUM)
Lighthouse はラボ環境のテストですが、実際のユーザー体験は異なる可能性があります。実際のユーザーから Core Web Vitals データを収集する必要があります。
Vercel Analytics の使用:
Vercel にデプロイしているなら、Analytics をオンにするだけです:
// app/layout.jsx
import { Analytics } from '@vercel/analytics/react';
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Analytics />
</body>
</html>
);
}
web-vitals ライブラリの使用:
Vercel 以外なら、Google の web-vitals ライブラリを使います:
npm install web-vitals
// app/layout.jsx
'use client';
import { useEffect } from 'react';
import { onLCP, onFID, onCLS, onINP } from 'web-vitals';
function sendToAnalytics(metric) {
// 分析サービスへ送信
fetch('/api/analytics', {
method: 'POST',
body: JSON.stringify(metric),
});
}
export default function Analytics() {
useEffect(() => {
onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);
}, []);
return null;
}
Google Search Console:
Google Search Console の「ウェブに関する主な指標」レポートには、実際のユーザーに基づいたサイトのパフォーマンスが表示され、改善が必要なページが特定されます。
よくある落とし穴と解決策
最後に、最も陥りやすい 5つの落とし穴をまとめました:
落とし穴 1: クライアントサイド JavaScript の過剰使用
React で何でもやろうとして、ページ全体が JavaScript に依存していませんか? JS の読み込みに失敗したり遅かったりすると、ユーザーは白屏を見ることになります。
解決策:
- 優先的に Server Components を使う(Next.js 13+ デフォルト)
- 必要な場合のみ ‘use client’ を使う
- プログレッシブエンハンスメントの思想:基本機能を確保した上でインタラクションを強化する
落とし穴 2: モバイルパフォーマンスの無視
デスクトップで速いからといって、モバイルでも速いとは限りません。モバイルデバイスは CPU が弱くネットワークも遅いため、問題が顕著になります。
解決策:
- Lighthouse の「モバイル」モードでテストする
- ネットワーク速度を制限してテスト(Chrome DevTools → Network → Throttling)
- CPU を制限してテスト(Chrome DevTools → Performance → CPU)
落とし穴 3: 未最適化のサードパーティスクリプト
Google Analytics, Facebook Pixel, Intercom, Hotjar… これらすべてがパフォーマンスを低下させます。
解決策:
- すべて
next/scriptでラップし、strategy="lazyOnload"を設定 - 定期見直し:本当に必要なスクリプトか? 減らせないか?
- クライアント追跡の代わりにサーバーサイド追跡を検討
落とし穴 4: 不適切な画像フォーマット
PNG や JPG をまだ使っていますか? WebP や AVIF ならサイズを 30-50% 削減できます。
解決策:
Next.js Image コンポーネントは自動変換しますが、サーバー設定が必要です:
// next.config.js
module.exports = {
images: {
formats: ['image/avif', 'image/webp'], // 最新フォーマットを優先
},
};
落とし穴 5: サーバーパフォーマンスの軽視
フロントエンドをいくら最適化しても、サーバー応答が遅ければ台無しです。
解決策:
- データベースクエリの最適化(インデックス追加、N+1 問題解消)
- キャッシュ利用(Redis, CDN)
- サーバーパフォーマンス監視(New Relic, Datadog などの APM ツール)
まとめ
今回お話しした内容を振り返ります:
LCP 最適化のポイント:
- LCP 画像に
priorityとfetchPriority="high"を設定 next/fontでフォント読み込みを最適化- SSG と ISR を優先し、サーバー計算を減らす
- CDN で TTFB を下げる
CLS 最適化のポイント:
- 全ての画像・メディア要素にサイズを指定
- 動的コンテンツのスペースを予約(広告、通知バー)
- JavaScript hooks によるレスポンシブレイアウトを避ける
next/fontでフォントのズレを軽減
FCP 最適化のポイント:
- 非重要リソースを遅延ロード
- サードパーティスクリプトは
next/scriptでlazyOnload - 大きなライブラリや非首屏コンポーネントを遅延ロード
継続的最適化:
- Lighthouse CI で自動テスト
- リアルユーザーデータ(RUM)を収集
- 定期的に不要コードをレビュー・削除
パフォーマンス最適化は「計測 → 最適化 → 再計測」のループです。一度で完了とは思わず、継続的に改善してください。
Lighthouse スコアが 90点を超えた時の達成感は格別です。そして何より、コンバージョン率の向上がその努力の価値を証明してくれるはずです。
アクションリスト
さあ、あなたの番です。以下の順序で始めることをお勧めします:
-
今すぐ実行:
- Next.js プロジェクトを Lighthouse でテスト
- LCP 要素を特定し、
priority設定があるか確認 useMediaQueryなど CLS の原因コードがないかチェック
-
今週中に完了:
- LCP 画像の最適化(priority + fetchPriority)
- フォントを
next/fontに切り替え - 動的コンテンツにプレースホルダーを追加
-
来週完了:
- サードパーティスクリプトを
lazyOnloadに変更 - 大型ライブラリと非首屏コンポーネントの遅延ロード
- Lighthouse CI 自動テストの設定
- サードパーティスクリプトを
-
継続項目:
- 毎月 Lighthouse スコアチェック
- Google Search Console の指標レポート確認
- パフォーマンス低下の即時修正
覚えておいてください:パフォーマンス最適化は一度きりのタスクではなく、継続的なプロセスです。最適化の成功を祈っています!
Next.js Core Web Vitals 完全最適化フロー
LCP、INP、CLS の3大コア指標を最適化し、Lighthouse スコアを 90+ に引き上げる
⏱️ Estimated time: 8 hr
- 1
Step1: LCP(最大コンテンツ描画)の最適化
目標:≤2.5秒
最適化手法:
• next/image で画像最適化(自動 WebP/AVIF 変換)
• 首屏の重要画像に priority 属性を追加
• 重要リソースのプリロード(<link rel="preload">)
• サーバー応答時間の短縮(CDN、Edge Functions 利用)
• レンダリングブロックリソースの削減(非重要 CSS/JS の遅延ロード)
• React Server Components でクライアント JS を削減
チェックツール:
• Lighthouse Performance レポート
• Chrome DevTools Performance パネル
• WebPageTest オンラインテスト - 2
Step2: INP(インタラクションから次の描画まで)の最適化
目標:≤200ms
最適化手法:
• JavaScript 実行時間の削減(コード分割、遅延ロード)
• React Server Components でクライアント JS を削減
• イベント処理の最適化(デバウンス、スロットル、イベント委譲)
• メインスレッドをブロックする長タスク回避(Web Workers 利用)
• サードパーティスクリプト最適化(遅延ロード、async/defer)
• Suspense とストリーミングレンダリングの活用
チェックツール:
• Chrome DevTools Performance パネル
• Web Vitals Chrome 拡張機能
• Real User Monitoring (RUM) ツール - 3
Step3: CLS(累積レイアウトシフト)の最適化
目標:≤0.1
最適化手法:
• 画像・動画の幅と高さを指定(または aspect-ratio 使用)
• 動的挿入コンテンツを避ける(スペース予約)
• font-display: swap の使用(フォント読み込みによるズレ回避)
• 広告スペースの確保(読み込み後の押し出し回避)
• 絶対配置ではなく CSS Grid/Flexbox を使用
• 既存コンテンツ上部への新コンテンツ挿入を避ける
チェックツール:
• Lighthouse CLS レポート
• Chrome DevTools Performance パネルの Layout Shift 記録
• WebPageTest 可視化 CLS - 4
Step4: Next.js パフォーマンス機能の活用
Next.js 最適化テクニック:
• Image コンポーネントで画像自動最適化
• Font Optimization でフォント自動最適化
• React Server Components でクライアント JS 削減
• Streaming SSR で首屏速度向上
• Dynamic Imports でコード分割
• next/dynamic でコンポーネント遅延ロード
設定チェック:
• next.config.js のパフォーマンス関連設定
• バンドルサイズ確認(npm run build) - 5
Step5: 監視と継続的改善
監視ツール:
• Google Search Console Core Web Vitals レポート
• Vercel Analytics(Vercel 利用時)
• Web Vitals Chrome 拡張機能
• Real User Monitoring (RUM) ツール
継続的改善:
• 毎月の Lighthouse スコアチェック
• Search Console レポートの注視
• パフォーマンス低下の即時修正
• A/B テストによる効果検証
FAQ
Core Web Vitals の3つの指標とは?
LCP の最適化方法は?
INP と FID の違いは?
CLS(レイアウトシフト)を防ぐには?
パフォーマンス最適化の効果はいつ出ますか?
Next.js のどの機能が最適化に役立ちますか?
Core Web Vitals の監視方法は?
9 min read · 公開日: 2025年12月19日 · 更新日: 2026年1月22日
関連記事
Next.js ファイルアップロード完全ガイド:S3/Qiniu Cloud 署名付き URL 直接アップロード実践
Next.js ファイルアップロード完全ガイド:S3/Qiniu Cloud 署名付き URL 直接アップロード実践
Next.js Eコマース実践:カートと Stripe 決済の完全実装ガイド
Next.js Eコマース実践:カートと Stripe 決済の完全実装ガイド
Next.js ユニットテスト実践:Jest + React Testing Library 完全設定ガイド

コメント
GitHubアカウントでログインしてコメントできます