7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Frontend Performance - Part 16] 配信最適化の総仕上げ:Code Splitting・Cache・CDN戦略まとめ

7
Posted at

ChatGPT Image May 15, 2026, 11_38_12 AM.png

📝 注意
本記事はAIの補助を受けて編集しています。


📚 目次


0. はじめに:Code Split、Cache、CDN – 配信高速化の三本柱

こんな経験はありませんか?

  • 再レンダリングやメモ化、state設計を徹底的に最適化したのに Lighthouseで「未使用JavaScriptを削減」と警告される
  • コード分割や遅延読み込みを十分にやったのに 遠隔地のユーザーから「遅い」と報告される
  • キャッシュ戦略を入れても デプロイのたびにユーザーが全リソースを再ダウンロードしてしまう

問題点:個別の最適化だけでは不十分です。Code Splitting、Caching、CDNを統合した戦略が必要です。

多くの記事はそれぞれの分野にしか触れません。この記事では3本柱を総合的にまとめ、次の問いに答えます:

「Code Splitting、Caching、CDNをどのように連携させれば、世界中のどこにいるユーザーにも最速の読み込み体験を提供できるのか?」


1. 配信最適化の三本柱

Part 13 (Code Splitting)、Part 14 (Caching)、Part 15 (CDN & Network) を踏まえ、以下の3つの基礎レイヤーがあります。

目的 主な手段
Code Splitting 初期ロードのコード量を減らす React.lazy、ルートベース分割、manualChunks
Caching 一度読み込んだリソースを再利用 HTTPキャッシュ、Service Worker、React Query
CDN & Network コンテンツをユーザーに地理的に近づける CDN、HTTP/3、Resource Hints、103 Early Hints

この3つは独立したものではなく、相互補完的です。

  • Code Splittingで転送量を減らす
  • Cachingで再訪問時の転送をゼロに近づける
  • CDNで物理的な距離を縮める

どれか一つだけやっても、他の部分でボトルネックが残ります。


2. Code Splitting – 初期ロードのコード量を減らす

2.1. 優先順位付き分割戦略

ルートベース分割 > コンポーネントベース分割 > ベンダーチャンク分割
戦略 適用タイミング 効果(目安)
ルートベース SPAやクライアントサイドが重いReactアプリでは最初に検討すべき 初期バンドルを大幅削減
コンポーネントベース 30KB超の重いコンポーネントがインタラクション後に出現 さらに10〜30%削減
ベンダーチャンク 大規模ライブラリ(チャート、UIフレームワーク) キャッシュ効率向上、重複削除

例:React Router + Vite によるルートベース分割

// App.tsx
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

const HomePage = lazy(() => import('./pages/HomePage'));
const ProductsPage = lazy(() => import('./pages/ProductsPage'));
const CheckoutPage = lazy(() => import('./pages/CheckoutPage'));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<GlobalSpinner />}>
        <Routes>
          <Route path="/" element={<HomePage />} />
          <Route path="/products" element={<ProductsPage />} />
          <Route path="/checkout" element={<CheckoutPage />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

ViteのmanualChunks設定例

// vite.config.ts
export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          'vendor-react': ['react', 'react-dom'],
          'vendor-ui': ['@mui/material', '@emotion/react'],
          'vendor-charts': ['recharts', 'd3']
        }
      }
    }
  }
});

2.2. 過分割(over‑splitting)に注意

チャンクを細かくしすぎるとリクエスト数が増え、モバイルネットワークではオーバーヘッドが顕著になります。ファイルサイズだけでなく、ネットワークウォーターフォールやJavaScriptのパース/実行コストも監視しましょう。5KBのチャンクが100個あると、リクエスト数と待ち時間が増大する可能性があります。

2.3. CDNキャッシュと動的チャンク

fetch()で読み込まれるチャンクも、通常はHTTP GETリクエストです。適切なCache-ControlヘッダとETagがあればCDNでキャッシュ可能です。キャッシュが効かない主な原因は:

  • no-storeprivateヘッダ
  • URLにセッションIDや認証トークンが含まれる
  • CDN設定で.js拡張子がキャッシュ対象外

原則: GETリクエストであること、Cache-Controlが適切であること、URLが安定していることを確認しましょう。


3. 圧縮 – Brotli、Gzip、画像最適化

CDNの前に、基本的な圧縮設定が正しく行われていることを確認してください。

  • Brotli をテキストアセット(JS, CSS, HTML)に有効に – Gzipよりも圧縮率が高く、モダンブラウザはすべて対応。
  • Gzip を古いブラウザ用フォールバックとして設定。
  • WebP / AVIF を画像に使用 – JPEG/PNGより30〜50%削減。
  • レスポンシブ画像srcset, sizes)で画面サイズに合った画像だけを読み込む。
# Nginx設定例
gzip on;
gzip_types text/plain text/css application/javascript application/json;
brotli on;
brotli_types text/plain text/css application/javascript application/json;

4. キャッシュ – HTTP CacheとService Worker

4.1. データ種別ごとのキャッシュ戦略

データ種別 戦略 Cache-Control
静的アセット (JS, CSS, フォント, 画像) Cache First public, max-age=31536000, immutable
HTML (メインページ) Revalidate no-cache (ETag/Last-Modified)
古いデータを許容できるAPI Stale‑While‑Revalidate max-age=60, stale-while-revalidate=30
リアルタイムAPI (カート、価格) Network First no-cache または非常に短いTTL

ハッシュ付きアセットの推奨設定

Cache-Control: public, max-age=31536000, immutable

4.2. Service Workerとキャッシュ無効化

新しいバージョンをデプロイする際、キャッシュ無効化は最大の課題です。

  • ハッシュ付きファイル名(例:main.a1b2c3.js):内容が変わるとファイル名も変わる → キャッシュ自動無効化。
  • バージョン付きキャッシュキー:Service Workerでバージョンごとにキャッシュ名を変え(例:my-app-v2)、activateで古いキャッシュを削除。
  • UXの考慮:強制的に即時有効化する代わりに「新しいバージョンがあります → 更新してください」と表示するほうが、バージョンミスマッチによるエラーを防げます。

最小限のService Worker例(本番未検証、概念用)

self.addEventListener('fetch', (event) => {
  if (event.request.method !== 'GET') return;
  event.respondWith(
    caches.match(event.request).then(response => response || fetch(event.request))
  );
});

警告

  • 上記はあくまで概念説明用です。本番ではWorkboxなどを使用し、stale-while-revalidateやキャッシュクリーンアップを含めた堅牢な設計をしてください。
  • 認証情報や個人データを含むレスポンスは、明確な無効化戦略とセキュリティ対策がない限りキャッシュしないでください。

5. CDNとネットワーク – コンテンツをユーザーに近づける

5.1. HTTP/3プロトコル

HTTP/3はTCPの代わりにQUIC (UDP) を使用し、以下の利点があります:

  • RTT削減: ハンドシェイク0〜1RTT(従来は2〜3RTT)。
  • ヘッドオブラインブロッキング解消: パケットロスが他のストリームをブロックしない。
  • モバイルや不安定なWi-Fiで大幅に改善(改善幅はパケットロス率やRTT、ネットワーク状況に依存)。
プラットフォーム HTTP/3有効化方法
Cloudflare / Fastly ダッシュボードでトグルをON
Nginx ngx_http_v3_module が必要(バージョン1.25+)
Caddy HTTPS設定で自動対応

5.2. Resource Hints – 待機時間を活用

ヒント 使用タイミング 節約目安
dns-prefetch 使うかもしれないサードパーティドメイン 20‑50ms
preconnect 確実に使う重要なオリジン(最大2〜3) 100‑300ms
preload LCPリソース(フォント、CSS、画像) LCP遅延削減
prefetch 次に遷移しそうなルート(SPA) ナビゲーション高速化

<link rel="preconnect" href="https://api.example.com" crossorigin />
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin />

5.3. 103 Early Hints

103 Early Hintsを使うと、サーバーはHTMLの生成が完了する前にLinkヘッダでプリロード指示を送れます。SSR主体の動的コンテンツで特に有効です。ただしSafariは未対応(2026年現在)。

HTTP/1.1 103 Early Hints
Link: </style.css>; rel=preload; as=style
Link: </hero.jpg>; rel=preload; as=image

5.4. CDNでのキャッシュ無効化の落とし穴

  • HTMLを強くキャッシュしすぎる → デプロイ後に古い内容が表示される。
  • APIレスポンスを誤ってキャッシュ → 別ユーザーのデータを返す。
  • キャッシュパージ戦略の欠如 → 古いアセットがいつまでも消えない。
  • キャッシュタグを使わない → グループ単位でのパージが困難。

基本ルール:静的アセット → 長期TTL + ハッシュ付きファイル名。HTML/API → 短いTTL + 再検証。

5.5. エッジコンピューティング(2026年)

Cloudflare Workers、Vercel Edge、Deno Deploy などのプラットフォームでは、コード(HTMLレンダリングやリクエスト処理)をエッジ(ユーザーに最も近い場所)で実行できます。

適しているケース

  • パーソナライズ、A/Bテスト、地理位置情報を使う動的コンテンツ
  • APIルーティング、認証チェック
  • CDNキャッシュとエッジコンピュートの組み合わせ

適さないケース

  • 大規模データベースアクセスが必須でレイテンシーを許容できないワークロード
  • エッジのカバレッジが限られているリージョン

現代のエッジランタイムではコールドスタートは大きな問題ではなくなっています。代わりにデータ局所性、ランタイム制限(CPU、メモリ)、コストを考慮しましょう。

原則:エッジは軽量ロジックに向いています。重い計算や大規模データベースクエリには適しません。


6. 三本柱の統合:実践的な連携モデル

実際の連携手順

順序 アクション
1 CDN + HTTP/3 転送経路を短縮、RTT削減
2 Resource Hints preconnect, preloadでLCPリソースを先行取得
3 HTTP Cache 静的アセットをキャッシュ(ハッシュ付き、immutable
4 Service Worker アプリシェルをキャッシュ、オフラインフォールバック
5 Code Splitting 現在のルートに必要なコードだけを読み込む
6 遅延読み込み チャートやモーダルなどは実際に必要になった時に読み込む

Next.jsとの統合例

// app/page.tsx – エッジランタイム
export const runtime = 'edge';

// next.config.ts – CDNカスタムドメイン
module.exports = {
  assetPrefix: process.env.NEXT_PUBLIC_CDN_URL,
};
<!-- Document.tsx – Resource Hints -->
<link rel="preconnect" href={process.env.NEXT_PUBLIC_CDN_URL} />
<link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin />

7. 2026年のトレンド:HTTP/3、エッジコンピューティング、AI

HTTP/3 – 急速に広がる新標準

主要CDN(Cloudflare, Fastly)ではHTTP/3が標準で有効化されています。もはや「新しい機能」ではなく、グローバルパフォーマンスの事実上の標準です。

エッジコンピューティング – 主流に

エッジコンピュートはコストも安定し、グローバルアプリケーションの現実的なソリューションとなっています。

AIによる配信最適化支援

  • 分割ポイントの自動提案:バンドルを解析し、最適なmanualChunksを提案。
  • キャッシュ無効化の問題を自動検出:ログを分析し、適切なTTLを提案。
  • Resource Hintsの自動調整:実際のユーザー行動に基づいて動的にヒントを追加。

注意:AIは強力なアシスタントですが、最終的なアーキテクチャの決定は人間が行う必要があります。AIに完全に任せるべきではありません。


8. アーキテクト向けチェックリスト

Code Splitting

  • SPAやクライアントサイドが重いアプリでは、ルートベース分割を最初に検討する。
  • チャンクはGETリクエストで読み込まれ、キャッシュヘッダが適切に設定されている。
  • 30KB超の重いコンポーネントは React.lazy + Suspense で遅延読み込みする。
  • ベンダーチャンク(react, react-dom, UIライブラリ)は分離する。
  • 過分割を避け、ネットワークウォーターフォールとパース/実行コストを監視する。

圧縮

  • テキストアセットに Brotli を有効にする。
  • 画像は WebP/AVIF を使用し、srcset + sizes を設定する。
  • 古いブラウザ用にGzipフォールバックを用意する。

キャッシュ

  • 静的アセットに Cache-Control: max-age=31536000, immutable を設定。
  • ハッシュ付きファイル名でキャッシュ無効化を自動化。
  • HTMLには no-cache または非常に短いTTLを設定。
  • Service Workerでアプリシェルをキャッシュし、activateで古いキャッシュを削除。
  • 「新しいバージョンがあります → 更新してください」UIを検討する。
  • 認証情報や個人データを含むレスポンスは、明確な無効化戦略がない限りキャッシュしない。

CDN & Network

  • 静的アセットにCDNを有効にする。
  • HTTP/3 を有効にする(CDNダッシュボードで確認)。
  • preconnect は重要なオリジンに最大2〜3、preload はLCPリソースに使用。
  • preloadの過剰使用を避ける(帯域競合を引き起こす)。
  • キャッシュ無効化戦略を明確にする(URLパターンやタグでのパージ)。
  • 103 Early Hints(対応ブラウザに限る)を静的コアリソースに検討する(認証必須コンテンツには使用しない)。

監視

  • RUM(Datadog, New Relic, Sentry, Chrome UX Reportなど)でLCP, TTFB, キャッシュヒット率をリージョン別に監視する。
  • Lighthouse CIをパイプラインに組み込み、リグレッションを検出する。
  • HTTP/3の実際の適用状況を確認する(Cloudflare Analyticsなど)。

9. まとめと次回予告

多くの開発者はバンドルサイズだけに注目しがちです。しかし実際のユーザー体験は次の3つで決まります。

「何を送るか?」(Code Splitting)
「何を再利用するか?」(Caching)
「どこから送るか?」(CDN & Network)

これらを連携させて初めて、世界中のどこにいるユーザーにも最適な読み込み体験を提供できます。具体的な改善幅は、既存のアーキテクチャ、トラフィック分布、キャッシュヒット率に依存します。

戦略 効果(目安、コンテキスト依存)
Code Splitting ルートベース分割 + 遅延読み込み 初期バンドルを大幅削減(多くのプロジェクトで50〜70%)
Caching HTTP Cache + Service Worker 再読み込み時間を大幅短縮(静的アセットでは80〜100%も可能)
CDN & Network HTTP/3 + Resource Hints + Edge TTFBを明確に改善(構成次第で40〜70%)

👉 次回予告 (Part 17)
[Frontend Performance - Part 17] 最適化の前に計測せよ:Chrome DevToolsで性能問題を見つける方法


7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?