Skip to content
Published on

Webパフォーマンス最適化完全ガイド2025:Core Web Vitals、ローディング戦略、レンダリングパターン総まとめ

Authors

はじめに

Webパフォーマンスはユーザー体験(たいけん)とビジネス成果(せいか)に直接的(ちょくせつてき)な影響(えいきょう)を与(あた)えます。Googleの研究(けんきゅう)によると、ページロード時間(じかん)が1秒(びょう)から3秒(びょう)に増(ふ)えると離脱率(りだつりつ)が32%増加(ぞうか)し、5秒(びょう)になると90%増加(ぞうか)します。Amazonはページロード時間(じかん)が100ms増(ふ)えるたびに売上(うりあげ)が1%減少(げんしょう)すると報告(ほうこく)しています。

2025年(ねん)、GoogleはCore Web Vitalsを検索(けんさく)ランキング要素(ようそ)として強化(きょうか)し、INP(Interaction to Next Paint)がFIDを置(お)き換(か)えたことで、インタラクション性能(せいのう)の重要性(じゅうようせい)がさらに高(たか)まりました。この記事(きじ)では、Core Web Vitalsの最適化(さいてきか)から画像(がぞう)、JavaScript、CSS、フォント、レンダリングパターン、キャッシュ、ネットワーク、モニタリング、そしてNext.js特化(とっか)の最適化(さいてきか)まで、Webパフォーマンスの全(すべ)てをカバーします。


1. なぜパフォーマンスが重要(じゅうよう)なのか

1.1 ビジネスインパクト

パフォーマンスとビジネス指標:
├── ロード3秒超過 → 53%離脱 (Google)
├── 100ms遅延 → 売上1%減少 (Amazon)
├── 500ms遅延 → トラフィック20%減少 (Google)
├── 1秒改善 → コンバージョン率7%向上 (Walmart)
└── 2秒改善 → バウンス率50%減少 (COOK)

1.2 SEOとCore Web Vitals

2025年(ねん)、GoogleはCore Web Vitalsを検索(けんさく)ランキングの核心(かくしん)要素(ようそ)として確定(かくてい)しました。

指標(しひょう)良好(りょうこう)(Good)改善必要(かいぜんひつよう)不良(ふりょう)(Poor)
LCP2.5秒(びょう)以内(いない)2.5〜4.0秒4.0秒超過(ちょうか)
INP200ms以内200〜500ms500ms超過
CLS0.1以下(いか)0.1〜0.250.25超過

1.3 パフォーマンスバジェット

パフォーマンスバジェット例:
├── 初期ロードJS: 200KB以下 (gzip)
├── 初期ロードCSS: 50KB以下 (gzip)
├── 全ページサイズ: 1.5MB以下
├── LCP: 2.5秒以内
├── INP: 200ms以内
├── CLS: 0.1以下
├── Time to First Byte: 600ms以内
└── リクエスト数: 50個以下

2. Core Web Vitals深掘(ふかぼ)り分析(ぶんせき)

2.1 LCP(Largest Contentful Paint)

LCPはビューポート内(ない)で最(もっと)も大(おお)きなコンテンツ要素(ようそ)がレンダリングされる時間(じかん)です。

LCP対象要素:
├── img要素
├── video要素(ポスター画像)
├── CSS background-imageを持つ要素
├── テキストノードを含むブロックレベル要素
└── svg内のimage要素

LCP最適化(さいてきか)戦略(せんりゃく):

<!-- 1. ヒーロー画像のプリロード -->
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high" />

<!-- 2. LCP画像にfetchpriority設定 -->
<img src="/hero.webp" alt="Hero" fetchpriority="high" loading="eager" />

<!-- 3. サーバー応答時間の最適化 -->
<!-- TTFB目標: 200ms以内 -->
/* 4. フォントローディング最適化 */
@font-face {
  font-family: 'MyFont';
  src: url('/fonts/myfont.woff2') format('woff2');
  font-display: swap;
}

2.2 INP(Interaction to Next Paint)

INPはユーザーインタラクション(クリック、タップ、キーボード)から次(つぎ)のペイントまでの遅延(ちえん)時間(じかん)を測定(そくてい)します。

INP最適化戦略:
1. 長いタスクの分割
   - 50ms以上のタスクをより小さな単位に分割
   - requestIdleCallback、scheduler.yield()活用

2. メインスレッドの解放
   - Web Workerに重い計算を移動
   - 不要な同期JSの除去

3. イベントハンドラーの最適化
   - debounce / throttle適用
   - passiveイベントリスナー使用
// 長いタスク分割の例
async function processLargeList(items: Item[]) {
  const CHUNK_SIZE = 50;

  for (let i = 0; i < items.length; i += CHUNK_SIZE) {
    const chunk = items.slice(i, i + CHUNK_SIZE);
    processChunk(chunk);

    // ブラウザにレンダリングの機会を提供
    await scheduler.yield();
  }
}

// scheduler.yieldポリフィル
if (!('scheduler' in globalThis)) {
  (globalThis as any).scheduler = {
    yield: () => new Promise(resolve => setTimeout(resolve, 0))
  };
}

2.3 CLS(Cumulative Layout Shift)

CLSはページロード中(ちゅう)の予期(よき)しないレイアウトシフトの合計(ごうけい)です。

<!-- CLS防止: 画像にサイズを明示 -->
<img src="/photo.webp" width="800" height="600" alt="Photo" />

<!-- CLS防止: 動的コンテンツにmin-heightを設定 -->
<div style="min-height: 250px;">
  <!-- 広告や動的コンテンツ -->
</div>
/* CLS防止: フォントローディング時のレイアウトシフト最小化 */
@font-face {
  font-family: 'MyFont';
  src: url('/fonts/myfont.woff2') format('woff2');
  font-display: optional; /* またはswap */
  size-adjust: 100.5%;
  ascent-override: 95%;
  descent-override: 22%;
  line-gap-override: 0%;
}

CLSの主要(しゅよう)原因(げんいん)と解決策(かいけつさく):

原因(げんいん)解決策(かいけつさく)
サイズ未指定(みしてい)の画像width/heightまたはaspect-ratio属性使用
動的挿入(そうにゅう)コンテンツmin-heightでスペース予約(よやく)
Webフォント FOUT/FOITfont-display: optional + preload
動的広告(こうこく)固定(こてい)サイズコンテナ使用
遅(おく)れてロードされるCSSCritical CSSをインライン化

3. 画像(がぞう)最適化(さいてきか)

3.1 次世代(じせだい)フォーマット

画像フォーマット比較(同品質基準):
├── JPEG: 100KB (基準)
├── WebP: 70KB (-30%)
├── AVIF: 50KB (-50%)
└── JXL (JPEG XL): 55KB (-45%)

ブラウザサポート (2025):
├── WebP: 97%+ (IE非対応)
├── AVIF: 92%+ (Safari 16.4+)
└── JXL: Chromeで削除、Safari/Firefoxサポート
<!-- picture要素でフォーマットフォールバック -->
<picture>
  <source srcset="/hero.avif" type="image/avif" />
  <source srcset="/hero.webp" type="image/webp" />
  <img src="/hero.jpg" alt="Hero image" width="1200" height="600" />
</picture>

3.2 レスポンシブ画像(がぞう)

<!-- srcsetとsizesでレスポンシブ画像 -->
<img
  srcset="
    /hero-400w.webp 400w,
    /hero-800w.webp 800w,
    /hero-1200w.webp 1200w,
    /hero-1600w.webp 1600w
  "
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 80vw, 1200px"
  src="/hero-800w.webp"
  alt="Hero image"
  width="1200"
  height="600"
  loading="lazy"
  decoding="async"
/>

3.3 遅延(ちえん)ロードとブラープレースホルダー

<!-- ネイティブlazy loading -->
<img src="/photo.webp" loading="lazy" decoding="async" alt="Photo" />
// ブラープレースホルダーの実装
function BlurImage({ src, alt, width, height, blurDataURL }: ImageProps) {
  return (
    <div style={{ position: 'relative', width, height }}>
      {/* ブラープレースホルダー */}
      <img
        src={blurDataURL}
        alt=""
        style={{
          position: 'absolute',
          inset: 0,
          filter: 'blur(20px)',
          transform: 'scale(1.1)',
        }}
      />
      {/* 実際の画像 */}
      <img
        src={src}
        alt={alt}
        width={width}
        height={height}
        loading="lazy"
        decoding="async"
        onLoad={(e) => {
          e.currentTarget.style.opacity = '1';
        }}
        style={{ position: 'relative', opacity: 0, transition: 'opacity 0.3s' }}
      />
    </div>
  );
}

4. JavaScript最適化(さいてきか)

4.1 コード分割(ぶんかつ)(Code Splitting)

// React lazy + Suspense(ルートベース分割)
import { lazy, Suspense } from 'react';

const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));

function App() {
  return (
    <Suspense fallback={<LoadingSpinner />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}
// 条件付き動的import
async function handleExport() {
  // xlsxライブラリを必要な時だけロード
  const XLSX = await import('xlsx');
  const workbook = XLSX.utils.book_new();
  // ...
}

4.2 Tree Shaking

// 悪い例: ライブラリ全体をimport
import _ from 'lodash';
_.debounce(fn, 300);

// 良い例: 必要な関数のみimport
import debounce from 'lodash/debounce';
debounce(fn, 300);

// さらに良い例: ネイティブまたは軽量な代替を使用
// lodash.debounce: 1.4KB vs lodash: 72KB
// webpack-bundle-analyzerでバンドル分析
// next.config.js
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
});

module.exports = withBundleAnalyzer({
  // Next.js config
});

4.3 バンドル最適化(さいてきか)

// webpack splitChunks設定
module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
          priority: 10,
        },
        common: {
          minChunks: 2,
          priority: -10,
          reuseExistingChunk: true,
        },
      },
    },
  },
};

4.4 スクリプトローディング戦略(せんりゃく)

<!-- 通常: パース をブロック -->
<script src="app.js"></script>

<!-- async: 非同期ダウンロード、即時実行(パースブロック可能) -->
<script src="analytics.js" async></script>

<!-- defer: 非同期ダウンロード、DOMパース後に実行(順序保証) -->
<script src="app.js" defer></script>

<!-- module: deferと同じ動作 -->
<script type="module" src="app.js"></script>
スクリプトローディングタイムライン:
通常:   [HTMLパース...] [ダウンロード] [実行] [HTMLパース...]
async:  [HTMLパース......ダウンロード......] [実行] [HTMLパース...]
defer:  [HTMLパース......ダウンロード.............] [実行]

5. CSS最適化(さいてきか)

5.1 Critical CSS

<!-- Critical CSSをインライン化 -->
<head>
  <style>
    /* ファーストビューに必要な最小限のCSSのみインライン */
    body { margin: 0; font-family: system-ui; }
    .header { height: 60px; background: #fff; }
    .hero { height: 400px; display: flex; align-items: center; }
  </style>

  <!-- 残りのCSSは非同期ロード -->
  <link rel="preload" href="/styles.css" as="style"
        onload="this.onload=null;this.rel='stylesheet'" />
  <noscript><link rel="stylesheet" href="/styles.css" /></noscript>
</head>

5.2 CSS Containment

/* containでレンダリング範囲を制限 */
.card {
  contain: layout style paint;
  /* またはcontent-visibilityでオフスクリーン要素を最適化 */
  content-visibility: auto;
  contain-intrinsic-size: 0 300px;
}

/* will-changeでGPUアクセラレーションヒント */
.animated-element {
  will-change: transform;
  /* 注意: 乱用するとメモリ浪費 */
}

5.3 未使用(みしよう)CSSの除去(じょきょ)

// PurgeCSS設定 (postcss.config.js)
module.exports = {
  plugins: [
    require('@fullhuman/postcss-purgecss')({
      content: [
        './src/**/*.{js,jsx,ts,tsx}',
        './public/index.html'
      ],
      defaultExtractor: content =>
        content.match(/[\w-/:]+(?<!:)/g) || [],
      safelist: ['html', 'body', /^data-/]
    })
  ]
};

6. フォント最適化(さいてきか)

6.1 font-display戦略(せんりゃく)

/* swap: 代替フォント → Webフォント(FOUT発生、テキスト即時表示) */
@font-face {
  font-family: 'MyFont';
  font-display: swap;
  src: url('/fonts/myfont.woff2') format('woff2');
}

/* optional: 速ければWebフォント、遅ければ代替フォント維持(CLS最小) */
@font-face {
  font-family: 'MyFont';
  font-display: optional;
  src: url('/fonts/myfont.woff2') format('woff2');
}

6.2 フォントプリロード

<!-- コアフォントのプリロード -->
<link
  rel="preload"
  href="/fonts/inter-var.woff2"
  as="font"
  type="font/woff2"
  crossorigin
/>

6.3 Variable Fonts

/* 従来: 各ウェイト別に別ファイル(400, 500, 600, 700 = 4ファイル) */
/* Variable Font: 1ファイルで全ウェイト */
@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-variable.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-display: swap;
}

/* 使用 */
.light { font-weight: 300; }
.regular { font-weight: 400; }
.bold { font-weight: 700; }

6.4 フォントサブセット

# pyftsubsetで日本語フォントサブセット生成
# pip install fonttools brotli
pyftsubset NotoSansJP-Regular.otf \
  --text-file=japanese-chars.txt \
  --output-file=NotoSansJP-subset.woff2 \
  --flavor=woff2

# 結果: 4.5MB → 500KB(常用漢字+ひらがな+カタカナ基準)

7. レンダリングパターン

7.1 パターン比較表(ひかくひょう)

パターンTTFBFCPLCPTTISEO使用(しよう)場面(ばめん)
CSR速(はや)い遅(おそ)い遅い遅い悪(わる)いSPA、ダッシュボード
SSR遅い速い速い遅い良(よ)い動的(どうてき)コンテンツ、SEO必要
SSG非常(ひじょう)に速い非常に速い非常に速い速い非常に良いブログ、ドキュメント
ISR非常に速い非常に速い非常に速い速い非常に良いEコマース、ニュース
Streaming SSR速い非常に速い速い速い良い複雑(ふくざつ)な動的ページ

7.2 CSR(Client-Side Rendering)

CSRフロー:
Browser        Server
  │─ HTML要求 ────→│
  │←─ 空のHTML ────│
  │─ JS要求 ──────→│
  │←─ JSバンドル ──│
[パース/実行]  │─ API要求 ─────→│
  │←─ データ ──────│
[レンダリング]  ▼ 画面表示        ▼

7.3 SSR(Server-Side Rendering)

SSRフロー:
Browser        Server
  │─ HTML要求 ────→│
  │               │ [データフェッチ]
  │               │ [HTMLレンダリング]
  │←─ 完成HTML ───│
[画面表示]  │─ JS要求 ──────→│
  │←─ JSバンドル ──│
[Hydration]  ▼ インタラクティブ ▼

7.4 Streaming SSR

// Next.js App Router Streaming SSR
import { Suspense } from 'react';

async function SlowComponent() {
  const data = await fetchSlowData(); // 3秒かかる
  return <div>{/* データレンダリング */}</div>;
}

export default function Page() {
  return (
    <div>
      {/* 即時レンダリング */}
      <Header />
      <Hero />

      {/* ストリーミング: 準備できたら段階的に送信 */}
      <Suspense fallback={<Skeleton />}>
        <SlowComponent />
      </Suspense>

      {/* 即時レンダリング */}
      <Footer />
    </div>
  );
}

7.5 ISR(Incremental Static Regeneration)

// Next.js ISR
// app/products/[id]/page.tsx
export const revalidate = 3600; // 1時間ごとに再生成

async function ProductPage({ params }: { params: { id: string } }) {
  const product = await getProduct(params.id);

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
    </div>
  );
}

export default ProductPage;

// 静的パス生成
export async function generateStaticParams() {
  const products = await getTopProducts(100);
  return products.map(p => ({ id: p.id }));
}

8. キャッシュ戦略(せんりゃく)

8.1 HTTPキャッシュ

HTTPキャッシュ戦略:
├── 静的アセット(JS/CSS/画像)
Cache-Control: public, max-age=31536000, immutable
│   (ファイル名にハッシュ含む: app.abc123.js)
├── HTML
Cache-Control: public, max-age=0, must-revalidate
│   (常にサーバー確認)
├── APIレスポンス
Cache-Control: private, max-age=60, stale-while-revalidate=300
│   (60秒キャッシュ、5分間stale許容)
└── ユーザー固有データ
    Cache-Control: private, no-cache
    (毎回サーバー検証)

8.2 stale-while-revalidate

stale-while-revalidateの動作:
リクエスト1: [キャッシュミス] → サーバー → レスポンス + キャッシュ保存
リクエスト2: [キャッシュヒット, fresh] → 即時レスポンス
リクエスト3: [キャッシュヒット, stale]即時レスポンス(stale) + バックグラウンド更新
リクエスト4: [キャッシュヒット, fresh]即時レスポンス(更新済みデータ)
// SWRライブラリ(React)
import useSWR from 'swr';

function Profile() {
  const { data, error, isLoading } = useSWR('/api/user', fetcher, {
    revalidateOnFocus: true,
    revalidateOnReconnect: true,
    refreshInterval: 30000, // 30秒ごとに更新
  });

  if (isLoading) return <Skeleton />;
  if (error) return <Error />;
  return <UserCard user={data} />;
}

8.3 Service Workerキャッシュ

// Service Workerキャッシング戦略
// sw.js
const CACHE_NAME = 'app-v1';
const STATIC_ASSETS = [
  '/',
  '/styles.css',
  '/app.js',
  '/offline.html'
];

// インストール: 静的アセットをプリキャッシュ
self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then(cache =>
      cache.addAll(STATIC_ASSETS)
    )
  );
});

// フェッチ: Cache First + Network Fallback
self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then(cached => {
      if (cached) return cached;

      return fetch(event.request).then(response => {
        const clone = response.clone();
        caches.open(CACHE_NAME).then(cache =>
          cache.put(event.request, clone)
        );
        return response;
      }).catch(() => {
        return caches.match('/offline.html');
      });
    })
  );
});

8.4 CDNキャッシュ

CDNキャッシュ階層:
ユーザー → [ブラウザキャッシュ][CDN Edge][CDN Origin Shield][サーバー]

CDNヘッダー例:
静的アセット:
  Cache-Control: public, max-age=31536000, immutable
  CDN-Cache-Control: public, max-age=31536000

動的コンテンツ:
  Cache-Control: public, max-age=0, must-revalidate
  CDN-Cache-Control: public, max-age=60, stale-while-revalidate=300
  Surrogate-Control: max-age=3600

9. ネットワーク最適化(さいてきか)

9.1 リソースヒント

<!-- DNS Prefetch: 外部ドメインのDNSを先行解決 -->
<link rel="dns-prefetch" href="//api.example.com" />
<link rel="dns-prefetch" href="//cdn.example.com" />

<!-- Preconnect: DNS + TCP + TLSを先行接続 -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />

<!-- Prefetch: 次ページのリソースを先行ロード -->
<link rel="prefetch" href="/next-page.js" />

<!-- Preload: 現ページの重要リソースを優先ロード -->
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin />
<link rel="preload" href="/hero.webp" as="image" />

9.2 Priority Hints

<!-- fetchpriorityでリソース優先順位を指定 -->
<!-- ヒーロー画像: 高優先度 -->
<img src="/hero.webp" fetchpriority="high" />

<!-- オフスクリーン画像: 低優先度 -->
<img src="/below-fold.webp" fetchpriority="low" loading="lazy" />

<!-- 重要スクリプト: 高優先度 -->
<script src="/critical.js" fetchpriority="high"></script>

9.3 HTTP/2とHTTP/3

HTTP/1.1 vs HTTP/2 vs HTTP/3:
┌──────────────┬──────────────┬──────────────┬──────────────┐
│  機能         │  HTTP/1.1HTTP/2HTTP/3├──────────────┼──────────────┼──────────────┼──────────────┤
│ 多重化        │ 不可         │ 対応          │ 対応          │
│ ヘッダー圧縮  │ なし         │ HPACKQPACK│ サーバープッシュ│ なし        │ 対応          │ 廃止          │
HOLブロッキング│ TCPレベル    │ TCPレベル     │ 解決(QUIC)│ 転送プロトコル │ TCPTCPQUIC(UDP)│ 接続確立      │ TCP+TLSTCP+TLS0-RTT可能    │
└──────────────┴──────────────┴──────────────┴──────────────┘

10. パフォーマンスモニタリング

10.1 Lighthouse CI

# .github/workflows/lighthouse.yml
name: Lighthouse CI
on: [pull_request]

jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci && npm run build
      - name: Run Lighthouse CI
        uses: treosh/lighthouse-ci-action@v11
        with:
          configPath: './lighthouserc.json'
          uploadArtifacts: true
{
  "ci": {
    "assert": {
      "assertions": {
        "categories:performance": ["error", { "minScore": 0.9 }],
        "categories:accessibility": ["error", { "minScore": 0.9 }],
        "categories:best-practices": ["warn", { "minScore": 0.9 }],
        "categories:seo": ["error", { "minScore": 0.9 }]
      }
    }
  }
}

10.2 Real User Monitoring(RUM)

// Web Vitalsの測定と報告
import { onLCP, onINP, onCLS, onFCP, onTTFB } from 'web-vitals';

function sendToAnalytics(metric: any) {
  const body = JSON.stringify({
    name: metric.name,
    value: metric.value,
    rating: metric.rating,
    delta: metric.delta,
    id: metric.id,
    navigationType: metric.navigationType,
  });

  // Beacon APIで非同期送信
  if (navigator.sendBeacon) {
    navigator.sendBeacon('/api/vitals', body);
  } else {
    fetch('/api/vitals', { body, method: 'POST', keepalive: true });
  }
}

onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);
onFCP(sendToAnalytics);
onTTFB(sendToAnalytics);

10.3 パフォーマンスダッシュボード

パフォーマンスモニタリングツール:
├── 合成モニタリング(Lab Data)
│   ├── Lighthouse CI(自動化)
│   ├── WebPageTest(詳細分析)
│   └── PageSpeed Insights(Google)
├── 実ユーザーモニタリング(Field Data)
│   ├── Chrome UX Report(CrUX)
│   ├── web-vitalsライブラリ
│   └── 商用: Datadog RUM、New Relic
└── バンドル分析
    ├── webpack-bundle-analyzer
    ├── source-map-explorer
    └── bundlephobia.com

11. Next.js特化(とっか)最適化(さいてきか)

11.1 App RouterとServer Components

// Server Component(デフォルト - JSバンドルに含まれない)
// app/products/page.tsx
async function ProductsPage() {
  // サーバーで直接データフェッチ(APIルート不要)
  const products = await db.product.findMany();

  return (
    <div>
      <h1>Products</h1>
      {products.map(p => (
        <ProductCard key={p.id} product={p} />
      ))}
    </div>
  );
}

// Client Component(インタラクションが必要な部分のみ)
// components/AddToCart.tsx
'use client';

import { useState } from 'react';

export function AddToCart({ productId }: { productId: string }) {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(c => c + 1)}>
      Add to Cart ({count})
    </button>
  );
}

11.2 Next.js Image最適化(さいてきか)

import Image from 'next/image';

// 自動WebP/AVIF変換、レスポンシブ、lazy loading
function HeroSection() {
  return (
    <Image
      src="/hero.jpg"
      alt="Hero"
      width={1200}
      height={600}
      priority  // LCP画像にはpriority設定
      sizes="(max-width: 768px) 100vw, 1200px"
      quality={85}
      placeholder="blur"
      blurDataURL="data:image/jpeg;base64,..."
    />
  );
}

// リモート画像
function Avatar({ user }: { user: User }) {
  return (
    <Image
      src={user.avatarUrl}
      alt={user.name}
      width={48}
      height={48}
      loading="lazy"
    />
  );
}

11.3 Next.js Font最適化(さいてきか)

// next/fontで自動最適化(セルフホスティング、CLS除去)
import { Inter, Noto_Sans_JP } from 'next/font/google';

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-inter',
});

const notoSansJP = Noto_Sans_JP({
  subsets: ['latin'],
  weight: ['400', '500', '700'],
  display: 'swap',
  variable: '--font-noto-sans-jp',
});

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="ja" className={`${inter.variable} ${notoSansJP.variable}`}>
      <body>{children}</body>
    </html>
  );
}

11.4 Route Segment Config

// app/blog/[slug]/page.tsx

// 静的生成 + ISR(60秒)
export const revalidate = 60;

// または完全に静的
export const dynamic = 'force-static';

// または常に動的
export const dynamic = 'force-dynamic';

// ランタイム選択
export const runtime = 'edge'; // Edge Runtime(速いTTFB)
// export const runtime = 'nodejs'; // Node.jsランタイム(デフォルト)

12. 面接(めんせつ)質問集(しつもんしゅう)

基本概念(きほんがいねん)

Q1. Core Web Vitals 3つの指標(しひょう)を説明(せつめい)してください。
  1. LCP(Largest Contentful Paint): ビューポートで最(もっと)も大(おお)きなコンテンツ要素(ようそ)がレンダリングされる時間(じかん)。目標(もくひょう): 2.5秒以内(いない)。ローディング性能(せいのう)を測定(そくてい)。

  2. INP(Interaction to Next Paint): ユーザーインタラクション(クリック、キーボードなど)から次(つぎ)のペイントまでの遅延(ちえん)。目標: 200ms以内。応答性(おうとうせい)を測定。

  3. CLS(Cumulative Layout Shift): ページロード中(ちゅう)の予期(よき)しないレイアウトシフトの合計(ごうけい)。目標: 0.1以下(いか)。視覚的(しかくてき)安定性(あんていせい)を測定。

Q2. CSR、SSR、SSG、ISRの違(ちが)いを説明(せつめい)してください。

CSR(Client-Side Rendering): ブラウザでJSを使(つか)ってレンダリング。空(から)のHTMLを送信(そうしん)後(ご)、クライアントでレンダリング。

  • 長所(ちょうしょ): サーバー負荷(ふか)低(ひく)い、SPA向(む)き
  • 短所(たんしょ): 初期(しょき)ロードが遅(おそ)い、SEO不利(ふり)

SSR(Server-Side Rendering): サーバーがリクエストごとにHTMLを生成(せいせい)。

  • 長所: 速(はや)いFCP、SEO有利(ゆうり)
  • 短所: サーバー負荷、遅いTTFB

SSG(Static Site Generation): ビルド時(じ)にHTMLを事前(じぜん)生成。

  • 長所: 最高(さいこう)のパフォーマンス、CDNキャッシュ
  • 短所: ビルド時間、動的(どうてき)コンテンツの制限(せいげん)

ISR(Incremental Static Regeneration): SSG + バックグラウンド再生成(さいせいせい)。

  • 長所: SSGのパフォーマンス + データ更新(こうしん)
  • 短所: 実装(じっそう)の複雑さ(ふくざつさ)
Q3. Code SplittingとTree Shakingの違(ちが)いを説明(せつめい)してください。

Code Splitting: JSバンドルを複数(ふくすう)のチャンクに分割(ぶんかつ)し、必要(ひつよう)なものだけをロードする技法(ぎほう)。ルートベース(dynamic import)またはコンポーネントベースで分割。初期(しょき)ロード時間(じかん)の短縮(たんしゅく)。

Tree Shaking: ビルド時(じ)に使用(しよう)されていない(dead)コードを除去(じょきょ)する最適化(さいてきか)。ES Moduleの静的(せいてき)構造(こうぞう)を分析(ぶんせき)して、importされていないexportを除去。最終(さいしゅう)バンドルサイズの削減(さくげん)。

違い: Code Splittingは「いつロードするか」(When)、Tree Shakingは「何(なに)を除去するか」(What)。

Q4. LCPを改善(かいぜん)する方法(ほうほう)を説明(せつめい)してください。
  1. サーバー応答最適化(さいてきか): TTFB 200ms以内(いない)、CDN活用(かつよう)、キャッシュ
  2. リソースプリロード: LCP画像(がぞう)にpreload + fetchpriority="high"
  3. 画像最適化: WebP/AVIF、適切(てきせつ)なサイズ、圧縮(あっしゅく)
  4. レンダリングブロック除去(じょきょ): Critical CSSインライン、JS defer
  5. フォント最適化: font-display: swap、preload
  6. SSR/SSG活用: サーバーから完成(かんせい)HTMLを送信
  7. サードパーティ最適化: 外部(がいぶ)スクリプトの遅延(ちえん)ロード
Q5. CLSの主要(しゅよう)な原因(げんいん)と解決方法(かいけつほうほう)を説明してください。

主要な原因:

  1. サイズ未指定(みしてい)の画像(がぞう)/動画(どうが)
  2. 動的挿入(どうてきそうにゅう)コンテンツ(広告(こうこく)、バナー)
  3. Webフォントロード時(じ)のレイアウトシフト
  4. 遅(おく)れてロードされるCSS
  5. DOMを操作(そうさ)するJavaScript

解決方法:

  1. img/videoにwidth、height属性(ぞくせい)またはaspect-ratio CSSを明示(めいじ)
  2. 動的コンテンツ領域(りょういき)にmin-heightでスペース予約(よやく)
  3. font-display: optional、フォントプリロード
  4. Critical CSSインライン化
  5. transformアニメーション使用(top/leftの代(か)わりに)

深掘(ふかぼ)り質問(しつもん)

Q6. Service Workerのキャッシング戦略(せんりゃく)を説明(せつめい)してください。
  1. Cache First: キャッシュ確認(かくにん)後(ご)、なければネットワーク。静的(せいてき)アセットに最適(さいてき)。
  2. Network First: ネットワーク試行(しこう)後(ご)、失敗(しっぱい)時(じ)にキャッシュ。APIレスポンスに最適。
  3. Stale While Revalidate: キャッシュされたレスポンスを即時(そくじ)返(かえ)す + バックグラウンドで更新(こうしん)。頻繁(ひんぱん)に変更(へんこう)されるアセットに最適。
  4. Cache Only: キャッシュのみ使用。オフラインアセットに最適。
  5. Network Only: ネットワークのみ使用。リアルタイムデータに最適。

選択基準(せんたくきじゅん): データの鮮度(せんど)要件(ようけん)とオフラインサポートの必要性(ひつようせい)に応(おう)じて決定(けってい)。

Q7. HTTPキャッシュヘッダーを説明(せつめい)してください。
  • Cache-Control: キャッシュポリシーの核心(かくしん)ヘッダー

    • max-age: キャッシュ有効期間(ゆうこうきかん)(秒(びょう))
    • no-cache: 毎回(まいかい)サーバー検証(けんしょう)必要
    • no-store: 絶対(ぜったい)にキャッシュしない
    • public/private: CDNキャッシュ可否(かひ)
    • immutable: 変(か)わらないリソース
    • stale-while-revalidate: staleレスポンス許容(きょよう)時間
  • ETag: リソースバージョン識別子(しきべつし)。条件付(じょうけんつ)きリクエスト(If-None-Match)に使用。

  • Last-Modified: 最終(さいしゅう)更新(こうしん)時間。If-Modified-Sinceと併用(へいよう)。

推奨戦略(すいしょうせんりゃく):

  • 静的アセット(ハッシュ含(ふく)む): max-age=31536000, immutable
  • HTML: no-cacheまたはmax-age=0, must-revalidate
Q8. 画像(がぞう)最適化(さいてきか)戦略を総合的(そうごうてき)に説明(せつめい)してください。
  1. フォーマット: WebP/AVIF使用(しよう)、picture要素(ようそ)でフォールバック
  2. サイズ: レスポンシブ画像(srcset + sizes)、実際(じっさい)の表示(ひょうじ)サイズに合(あ)わせる
  3. 圧縮(あっしゅく): 品質75〜85、用途(ようと)に応(おう)じて調整(ちょうせい)
  4. ローディング: LCP画像はeager + fetchpriority="high"、残(のこ)りはlazy
  5. フレームワーク: Next.js Imageなど自動最適化ツール活用(かつよう)
  6. CDN: 画像CDN(Cloudinary、imgix)活用
  7. プレースホルダー: ブラーまたはLQIPでUX向上(こうじょう)
Q9. INP(Interaction to Next Paint)を最適化(さいてきか)する方法(ほうほう)を説明してください。
  1. 長(なが)いタスクの分割(ぶんかつ): 50ms以上(いじょう)の作業(さぎょう)をscheduler.yield()やrequestIdleCallbackで分割
  2. メインスレッドの解放(かいほう): Web Workerに重(おも)い計算(けいさん)を移動(いどう)
  3. イベントハンドラー最適化: debounce/throttle、passiveイベントリスナー
  4. JSバンドル最小化(さいしょうか): コードスプリッティング、ツリーシェイキング
  5. 仮想化(かそうか): 長(なが)いリストはreact-virtuosoなどで仮想化
  6. startTransition: 緊急(きんきゅう)でない更新(こうしん)をトランジションとしてマーク
  7. React最適化: useMemo、useCallback、React.memoを適切(てきせつ)に使用
Q10. CDNの動作原理(どうさげんり)と性能上(せいのうじょう)の利点(りてん)を説明(せつめい)してください。

動作原理:

  1. 世界中(せかいじゅう)に分散(ぶんさん)されたEdgeサーバー(PoP)にコンテンツをキャッシュ
  2. ユーザーのDNSリクエストを最(もっと)も近(ちか)いEdgeにルーティング
  3. Edgeにキャッシュがあれば即時(そくじ)応答(おうとう)、なければOriginから取得(しゅとく)してキャッシュ

パフォーマンスの利点(りてん):

  • 物理的(ぶつりてき)距離(きょり)の短縮(たんしゅく): RTT(Round Trip Time)の削減(さくげん)
  • Originサーバーの負荷(ふか)分散(ぶんさん)
  • DDoS防御(ぼうぎょ)
  • TLS最適化(EdgeでTLS終端(しゅうたん))
  • 自動圧縮(じどうあっしゅく)(Brotli、gzip)
  • HTTP/2、HTTP/3サポート
Q11. webpackのコードスプリッティング設定(せってい)を説明(せつめい)してください。

webpackのsplitChunksプラグインでコード分割(ぶんかつ):

  1. Entry Points: 複数(ふくすう)のエントリーポイントで手動(しゅどう)分割
  2. Dynamic Imports: import()で動的(どうてき)分割
  3. splitChunks: 自動(じどう)分割ルール設定(せってい)

主要(しゅよう)な設定:

  • chunks: 'all'で同期(どうき)/非同期(ひどうき)の両方(りょうほう)を分割
  • cacheGroups: vendor(node_modules)とcommon(共有(きょうゆう)モジュール)を分離(ぶんり)
  • minSize: 最小(さいしょう)チャンクサイズ(デフォルト20KB)
  • maxSize: 最大(さいだい)チャンクサイズ(自動分割)

効果(こうか): 初期ロード時間短縮(たんしゅく)、キャッシュ効率(こうりつ)向上(こうじょう)、必要(ひつよう)なコードのみロード

Q12. Streaming SSRの利点(りてん)と実装方法(じっそうほうほう)を説明(せつめい)してください。

利点:

  • TTFB短縮(たんしゅく): HTMLを段階的(だんかいてき)に送信(そうしん)
  • FCP向上(こうじょう): 準備(じゅんび)できた部分(ぶぶん)から表示(ひょうじ)
  • 遅(おそ)いデータソースの隔離(かくり): Suspenseで囲(かこ)んで独立(どくりつ)ロード
  • UX向上: スケルトン/ローディング状態(じょうたい)を即時(そくじ)表示

実装(Next.js App Router):

  • Server Componentをデフォルトで使用
  • 遅いコンポーネントをSuspenseで囲む
  • fallbackにスケルトンUIを提供(ていきょう)
  • データが準備(じゅんび)できたら自動的(じどうてき)にストリーミング

核心(かくしん): ページ全体(ぜんたい)が準備されるまで待(ま)たず、準備できた部分から段階的(だんかいてき)に送信(そうしん)。

Q13. Next.js Server Componentsのパフォーマンス上(じょう)の利点(りてん)を説明(せつめい)してください。
  1. ゼロJSバンドル: Server ComponentsはクライアントJSバンドルに含(ふく)まれない
  2. 直接(ちょくせつ)データアクセス: サーバーで直接DB/APIにアクセス(APIルート不要(ふよう))
  3. 自動(じどう)コード分割(ぶんかつ): Client Componentsのみバンドルに含まれる
  4. ストリーミング: Suspenseと組(く)み合(あ)わせて段階的(だんかいてき)レンダリング
  5. キャッシュ: サーバー側(がわ)キャッシュで繰(く)り返(かえ)しリクエストを最適化(さいてきか)
  6. セキュリティ: 機密(きみつ)ロジック/キーがクライアントに露出(ろしゅつ)しない

使用原則(しようげんそく): インタラクションのないコンポーネントはServer、useState/useEffectが必要(ひつよう)な部分(ぶぶん)のみClient。

Q14. パフォーマンスバジェットの設定(せってい)と適用方法(てきようほうほう)は?

設定:

  1. 競合分析(きょうごうぶんせき): 主要(しゅよう)な競合(きょうごう)のパフォーマンス指標(しひょう)を測定(そくてい)
  2. ユーザーデバイス分析: ターゲットユーザーの平均(へいきん)デバイス/ネットワークを把握(はあく)
  3. ビジネス目標(もくひょう)の反映(はんえい): コンバージョン率、離脱率(りだつりつ)目標に合(あ)わせて設定
  4. 具体的(ぐたいてき)な数値(すうち): JS 200KB、CSS 50KB、LCP 2.5s、INP 200msなど

適用:

  1. Lighthouse CIでPRごとに自動検査(じどうけんさ)
  2. バジェット超過時(ちょうかじ)にビルド失敗(しっぱい)または警告(けいこく)
  3. bundlesizeまたはsize-limitでバンドルサイズ制限(せいげん)
  4. チームダッシュボードでトレンドモニタリング
  5. 定期的(ていきてき)にバジェットのレビューと調整(ちょうせい)
Q15. Webフォント最適化(さいてきか)のベストプラクティスを説明(せつめい)してください。
  1. Variable Font: 1つのファイルで全(すべ)てのウェイト/スタイル(ファイル数(すう)削減(さくげん))
  2. サブセット: 必要(ひつよう)な文字(もじ)のみ含(ふく)む(日本語(にほんご): 4MB → 500KB)
  3. WOFF2フォーマット: 最高(さいこう)圧縮率(あっしゅくりつ)のWebフォントフォーマット
  4. font-display: swap(テキスト即時(そくじ)表示(ひょうじ))またはoptional(CLS最小(さいしょう))
  5. preload: コアフォントをlink preloadで早期(そうき)ダウンロード
  6. セルフホスティング: Google Fontsの代(か)わりに直接(ちょくせつ)ホスティング(DNS/接続(せつぞく)コスト削減)
  7. size-adjust: 代替(だいたい)フォントとWebフォントのサイズを合(あ)わせてCLS防止(ぼうし)

13. クイズ

Q1. LCPの「Good」基準(きじゅん)は?

正解(せいかい): 2.5秒(びょう)以内(いない)

LCP(Largest Contentful Paint)の基準:

  • Good: 2.5秒以内
  • Needs Improvement: 2.5〜4.0秒
  • Poor: 4.0秒超過(ちょうか)

LCPはビューポートで最(もっと)も大(おお)きなコンテンツ要素(ようそ)がレンダリングされる時間(じかん)を測定(そくてい)します。

Q2. FIDを置(お)き換(か)えたCore Web Vitals指標(しひょう)は?

正解: INP(Interaction to Next Paint)

2024年(ねん)3月(がつ)からFID(First Input Delay)がINPに置(お)き換(か)えられました。FIDは最初(さいしょ)のインタラクションのみを測定(そくてい)しましたが、INPはページのライフサイクル全体(ぜんたい)のすべてのインタラクションを測定し、より包括的(ほうかつてき)な応答性(おうとうせい)指標(しひょう)を提供(ていきょう)します。

Q3. HTTPキャッシュにおけるimmutableの意味(いみ)は?

正解: リソースが絶対(ぜったい)に変(か)わらないことを示(しめ)し、再検証(さいけんしょう)リクエストを防止(ぼうし)する

Cache-Control: immutableはリソースが変更(へんこう)されないことをブラウザに伝(つた)えます。これにより、ブラウザがmax-age期間(きかん)内(ない)に再検証(304)リクエストを送(おく)らなくなります。ファイル名(めい)にハッシュが含(ふく)まれる静的(せいてき)アセット(app.abc123.js)に最適(さいてき)です。

Q4. Tree Shakingが動作(どうさ)するための前提条件(ぜんていじょうけん)は?

正解: ES Module(import/export)の使用(しよう)

Tree ShakingはES Moduleの静的(せいてき)構造(こうぞう)を分析(ぶんせき)して、使用(しよう)されていないexportを除去(じょきょ)します。CommonJS(require/module.exports)は動的(どうてき)であるためTree Shakingが不可能(ふかのう)です。package.jsonの"sideEffects": false設定(せってい)も重要(じゅうよう)です。

Q5. Next.js Server Componentsはクライアントバンドルに含(ふく)まれますか?

正解: いいえ、Server ComponentsはクライアントJSバンドルに含まれません

Server Componentsはサーバーでのみ実行(じっこう)され、結果(けっか)のHTMLのみがクライアントに送信(そうしん)されます。これによりJSバンドルサイズを大幅(おおはば)に削減(さくげん)できます。'use client'ディレクティブが宣言(せんげん)されたClient Componentsのみがバンドルに含まれます。


14. 参考(さんこう)資料(しりょう)

公式(こうしき)ドキュメント

測定(そくてい)ツール

画像最適化(がぞうさいてきか)

パフォーマンス参考(さんこう)

フレームワーク