言語を切り替える
テーマを切り替える

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つのパフォーマンスの落とし穴
95点
最適化後の Lighthouse
65点から向上
1.2秒
最適化後の LCP
3.5秒から短縮
28%
コンバージョン率向上
パフォーマンス向上がもたらした価値
47%
基準達成サイト率
2025年 Core Web Vitals データ
数据来源: 実戦データ

Core Web Vitals 2025年最新動向

最適化を始める前に、現在のルールを明確にしておきましょう。多くの記事ではまだ FID(初回入力遅延)について語られていますが、この指標は 2024年3月に廃止されました。

現在の3大コア指標

Google は現在、以下の3つの指標を重視しています:

  1. LCP (Largest Contentful Paint) - 最大コンテンツ描画

    • 目標: ≤ 2.5 秒
    • 読み込み性能を測定
    • Lighthouse スコアの 25% を占める
  2. INP (Interaction to Next Paint) - インタラクションから次の描画まで

    • 目標: ≤ 200ms
    • インタラクション応答速度を測定
    • 2024年3月に FID に代わって導入された新指標
  3. CLS (Cumulative Layout Shift) - 累積レイアウトシフト

    • 目標: < 0.1
    • 視覚的な安定性を測定

FCP(初回コンテンツ描画)はコア指標ではありませんが、ユーザーの第一印象に影響するため依然として重要です。

なぜこれらの指標に注目すべきか?

こんな経験はありませんか? Web サイトを開いてボタンをクリックしようとした瞬間、ページがガクッと動いて別の場所をクリックしてしまった。これが CLS の悪さです。

あるいは、サイトにアクセスしても数秒間真っ白な画面が続き、「ネットが切れたかな?」と思ったことは? これが LCP の遅さです。

Chrome チームのデータによると、トップクラスの Web サイトの LCP 平均値は約 1,220ms です。もしあなたのサイトの LCP が 2.5秒を超えているなら、競合他社に大きく遅れをとっています。

LCP 最適化 - メインコンテンツを最速で表示する

LCP は私が最適化に最も時間を費やした指標であり、効果も最も顕著でした。具体的な最適化手順を解説します。

ステップ1: LCP 要素を特定する

最適化の前に、どれが LCP 要素かを知る必要があります。通常、LCP 要素は以下のいずれかです:

  • ファーストビューのヒーロー画像
  • 動画のカバー画像
  • 大きな見出しやテキストブロック

素早い確認方法:

  1. Chrome DevTools を開く(F12)
  2. Ctrl+Shift+P(Mac は Cmd+Shift+P)を押す
  3. “Show Rendering” と入力
  4. “Core Web Vitals” にチェックを入れる
  5. ページをリロードすると、右上に 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 コンポーネントは widthheight の指定を求めます。これは意地悪ではなく、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 は複数のレンダリング方式を提供していますが、パフォーマンス順に並べると以下の通りです:

  1. SSG (Static Site Generation) - ビルド時に HTML 生成、最速
  2. ISR (Incremental Static Regeneration) - オンデマンドで静的ページ再生成
  3. SSR (Server-Side Rendering) - リクエストごとに HTML 生成、比較的遅い
  4. 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 が起きるのか?

  1. 初回レンダリング時、JS 未実行のため isMobileundefined かデフォルト値
  2. JS 実行後、useMediaQuery が実際の値を返す
  3. コンポーネントが再レンダリングされ、レイアウトが急変する

サーバーサイドレンダリング(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 画像に priorityfetchPriority="high" を設定
  • next/font でフォント読み込みを最適化
  • SSG と ISR を優先し、サーバー計算を減らす
  • CDN で TTFB を下げる

CLS 最適化のポイント:

  • 全ての画像・メディア要素にサイズを指定
  • 動的コンテンツのスペースを予約(広告、通知バー)
  • JavaScript hooks によるレスポンシブレイアウトを避ける
  • next/font でフォントのズレを軽減

FCP 最適化のポイント:

  • 非重要リソースを遅延ロード
  • サードパーティスクリプトは next/scriptlazyOnload
  • 大きなライブラリや非首屏コンポーネントを遅延ロード

継続的最適化:

  • Lighthouse CI で自動テスト
  • リアルユーザーデータ(RUM)を収集
  • 定期的に不要コードをレビュー・削除

パフォーマンス最適化は「計測 → 最適化 → 再計測」のループです。一度で完了とは思わず、継続的に改善してください。

Lighthouse スコアが 90点を超えた時の達成感は格別です。そして何より、コンバージョン率の向上がその努力の価値を証明してくれるはずです。

アクションリスト

さあ、あなたの番です。以下の順序で始めることをお勧めします:

  1. 今すぐ実行:

    • Next.js プロジェクトを Lighthouse でテスト
    • LCP 要素を特定し、priority 設定があるか確認
    • useMediaQuery など CLS の原因コードがないかチェック
  2. 今週中に完了:

    • LCP 画像の最適化(priority + fetchPriority)
    • フォントを next/font に切り替え
    • 動的コンテンツにプレースホルダーを追加
  3. 来週完了:

    • サードパーティスクリプトを lazyOnload に変更
    • 大型ライブラリと非首屏コンポーネントの遅延ロード
    • Lighthouse CI 自動テストの設定
  4. 継続項目:

    • 毎月 Lighthouse スコアチェック
    • Google Search Console の指標レポート確認
    • パフォーマンス低下の即時修正

覚えておいてください:パフォーマンス最適化は一度きりのタスクではなく、継続的なプロセスです。最適化の成功を祈っています!

Next.js Core Web Vitals 完全最適化フロー

LCP、INP、CLS の3大コア指標を最適化し、Lighthouse スコアを 90+ に引き上げる

⏱️ Estimated time: 8 hr

  1. 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. 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. 3

    Step3: CLS(累積レイアウトシフト)の最適化

    目標:≤0.1

    最適化手法:
    • 画像・動画の幅と高さを指定(または aspect-ratio 使用)
    • 動的挿入コンテンツを避ける(スペース予約)
    • font-display: swap の使用(フォント読み込みによるズレ回避)
    • 広告スペースの確保(読み込み後の押し出し回避)
    • 絶対配置ではなく CSS Grid/Flexbox を使用
    • 既存コンテンツ上部への新コンテンツ挿入を避ける

    チェックツール:
    • Lighthouse CLS レポート
    • Chrome DevTools Performance パネルの Layout Shift 記録
    • WebPageTest 可視化 CLS
  4. 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. 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(最大コンテンツ描画)≤2.5秒:読み込み性能。INP(インタラクション応答)≤200ms:応答速度(2024年にFIDを代替)。CLS(累積レイアウトシフト)≤0.1:レイアウト安定性。これらはGoogle検索順位に直接影響します。
LCP の最適化方法は?
1) next/image で画像最適化、2) 首屏重要画像に priority 属性、3) 重要リソースのプリロード、4) サーバー応答時間の短縮、5) レンダリングブロックリソース削減、6) React Server Components 利用。目標は LCP≤2.5秒です。
INP と FID の違いは?
INP は2024年3月に FID を置き換えた新指標です。FID は初回インタラクションのみ測定しますが、INP は全インタラクションの応答時間を測定します。より実際のユーザー体験を反映します。目標値は ≤200ms です。
CLS(レイアウトシフト)を防ぐには?
1) 画像・動画のサイズ指定、2) 動的コンテンツ回避、3) font-display: swap 利用、4) 広告スペース確保、5) CSS Grid/Flexbox 利用、6) 既存コンテンツ上部への挿入回避。目標は CLS≤0.1 です。
パフォーマンス最適化の効果はいつ出ますか?
技術指標(Lighthouseスコア)は即座に確認できます。ビジネス指標(コンバージョン率、検索順位)は通常1〜3ヶ月かかります。Google の再評価やユーザー行動データの蓄積に時間が必要です。継続的な監視が重要です。
Next.js のどの機能が最適化に役立ちますか?
React Server Components(クライアントJS削減)、Imageコンポーネント(画像最適化)、Font Optimization(フォント最適化)、Streaming SSR(首屏高速化)、Dynamic Imports(コード分割)、自動コード分割と tree-shaking。これらを活用すれば大幅な向上が見込めます。
Core Web Vitals の監視方法は?
1) Google Search Console(実ユーザーデータ)、2) Lighthouse(ラボデータ)、3) Web Vitals Chrome拡張、4) Vercel Analytics、5) Real User Monitoringツール。ラボデータと実データを併用することをお勧めします。

9 min read · 公開日: 2025年12月19日 · 更新日: 2026年1月22日

コメント

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

関連記事