Tips & Tricks (更新: 2026/6/2)

Claude Codeでコード分割と遅延読み込みを実装する実務ガイド

React/Next.jsのコード分割と遅延読み込みをClaude Codeで安全に進める手順、実装例、落とし穴を解説。

Claude Codeでコード分割と遅延読み込みを実装する実務ガイド

ReactやNext.jsのアプリが重くなると、最初に疑うべきものの一つがJavaScriptバンドルです。トップページを開いただけなのに、管理画面、グラフ、動画プレーヤー、リッチエディタまで一緒に読み込んでいるなら、読者は必要ないコードの到着を待たされています。

コード分割とは、大きなJavaScriptを小さなかたまりに分けることです。遅延読み込みとは、そのかたまりを「必要になったタイミング」で読み込むことです。初心者向けに言い換えると、旅行に行くたびに家中の荷物を持つのではなく、目的地で使う荷物だけを先に持つ考え方です。

この記事では、Claude Codeに丸投げするのではなく、設計、実装、レビュー、検証までを人間が判断できる形にします。ReactのlazySuspense、Next.jsのdynamic import、ルート単位の分割、hydrationの落とし穴まで扱います。関連して、読み込み速度全体はClaude Codeパフォーマンス最適化、不要コードの削除はツリーシェイキング、分割後の確認はバンドル分析も参照してください。

まず用語を整理する

bundleは、ブラウザへ送るJavaScriptのまとまりです。chunkは、コード分割後にできる小さなファイルです。dynamic importは、import("./Chart")のように実行時にモジュールを読み込む仕組みです。Suspenseは、まだ読み込み中のコンポーネントの代わりにローディングUIを見せる境界です。

hydrationは、サーバーが返したHTMLに、ブラウザ側のJavaScriptがイベントや状態をつなぎ直す処理です。ここでサーバーとクライアントの表示がズレると、hydration errorが起きます。遅延読み込みは便利ですが、ブラウザでしか動かない処理、時刻、乱数、window参照を混ぜると、このズレを起こしやすくなります。

コード分割で狙うべき場所は、最初の表示に不要で、サイズが大きく、ユーザー操作後に必要になる部分です。逆に、ファーストビューの見出し、本文、購入ボタン、ナビゲーションのような重要UIは、安易に遅延読み込みしません。

対象分割しやすい理由注意点
管理画面・設定画面一般読者には不要認証後のローディングを短くする
グラフ・地図・エディタ依存ライブラリが重い表示領域を固定してレイアウトシフトを防ぐ
モーダル・ウィザードクリック後にだけ必要初回クリックで遅すぎるなら事前読み込みする
動画・音声・検索UI初期表示と関係が薄いことが多いアクセシビリティとエラー文言を入れる

Claude Codeに渡す前の作業単位

Claude Codeへ「コード分割して」とだけ頼むと、分割しなくてよい部品まで分けたり、Suspenseの境界を大きくしすぎたりします。最初に、何を守るかを明文化します。

目的:
- 初期表示で不要な重いUIだけを遅延読み込みする
- ファーストビュー、CTA、ナビゲーション、本文は壊さない
- React.lazy / Suspense または Next.js dynamic を使う

対象:
- src/features/reports/ReportsPanel.tsx
- src/features/editor/RichEditor.tsx
- app/admin/page.tsx

検証:
- npm run lint
- npm run build
- ブラウザのNetworkで初期JSと遅延chunkを確認
- モバイル幅でローディングUIとCTAの重なりを確認

この単位で依頼すると、Claude Codeは「どのファイルをどう変えるか」を絞れます。特に既存サイトでは、速度改善よりも、問い合わせ、購入、研修申込などの導線を壊さないことが大事です。

React.lazyとSuspenseの基本実装

React公式ドキュメントでは、lazyでコンポーネントのコード読み込みを初回レンダリングまで遅らせ、読み込み中はSuspensefallbackを表示する流れが説明されています。重要なのは、lazyをコンポーネントの外側、つまりモジュールのトップレベルで宣言することです。内側で宣言すると再レンダリング時に状態がリセットされやすくなります。

以下はViteや一般的なReactアプリで試せる最小構成です。

// src/App.tsx
import { Suspense, lazy, useState } from "react";

const ReportsPanel = lazy(() => import("./ReportsPanel"));

function PanelSkeleton() {
  return (
    <div role="status" aria-live="polite" style={{ minHeight: 180 }}>
      レポートを読み込んでいます...
    </div>
  );
}

export default function App() {
  const [showReports, setShowReports] = useState(false);

  return (
    <main>
      <h1>ダッシュボード</h1>
      <button type="button" onClick={() => setShowReports(true)}>
        レポートを表示
      </button>

      {showReports ? (
        <Suspense fallback={<PanelSkeleton />}>
          <ReportsPanel />
        </Suspense>
      ) : (
        <p>必要なときだけ重いレポートUIを読み込みます。</p>
      )}
    </main>
  );
}
// src/ReportsPanel.tsx
const rows = [
  { label: "記事読了", value: "68%" },
  { label: "CTAクリック", value: "4.2%" },
  { label: "相談フォーム到達", value: "1.1%" },
];

export default function ReportsPanel() {
  return (
    <section aria-label="レポート">
      <h2>コンバージョンレポート</h2>
      <ul>
        {rows.map((row) => (
          <li key={row.label}>
            {row.label}: {row.value}
          </li>
        ))}
      </ul>
    </section>
  );
}

lazyはdefault exportを期待します。named exportしかないファイルをそのまま読み込むと動きません。その場合は、読み込み後にdefaultへ詰め替えます。

// src/lazyNamed.tsx
import { lazy, type ComponentType } from "react";

export function lazyNamed<TModule, TName extends keyof TModule>(
  loader: () => Promise<TModule>,
  name: TName
) {
  return lazy(async () => {
    const module = await loader();
    return {
      default: module[name] as ComponentType,
    };
  });
}
// src/AnalyticsSlot.tsx
import { Suspense } from "react";
import { lazyNamed } from "./lazyNamed";

const BarChart = lazyNamed(() => import("./charts"), "BarChart");

export function AnalyticsSlot() {
  return (
    <Suspense fallback={<p>グラフを読み込んでいます...</p>}>
      <BarChart />
    </Suspense>
  );
}

Next.jsではdynamic importを使い分ける

Next.jsでは、ページやルート単位の分割はフレームワーク側で自然に効きます。さらに、重いクライアントコンポーネントだけを遅らせたいときにnext/dynamicを使います。公式ドキュメントでは、import()dynamic()の中に書き、モジュールのトップレベルで宣言する形が推奨されています。レンダリング中に条件分岐でdynamic()を呼ぶのは避けます。

リッチエディタのようにブラウザAPIへ依存する部品は、Client Component側で分離します。

// app/admin/EditorSlot.tsx
"use client";

import dynamic from "next/dynamic";

const RichEditor = dynamic(() => import("./RichEditor"), {
  ssr: false,
  loading: () => (
    <p aria-live="polite" style={{ minHeight: 160 }}>
      エディタを読み込んでいます...
    </p>
  ),
});

export default function EditorSlot() {
  return <RichEditor initialMarkdown="# Draft" />;
}
// app/admin/page.tsx
import EditorSlot from "./EditorSlot";

export default function AdminPage() {
  return (
    <main>
      <h1>記事編集</h1>
      <p>本文とCTAは先に表示し、重いエディタだけを遅延読み込みします。</p>
      <EditorSlot />
    </main>
  );
}

ssr: falseは便利ですが、SEOに必要な本文や商品説明へ使うべきではありません。サーバーHTMLに出なくなるため、読者も検索エンジンも重要情報を最初に見られません。管理画面、プレビュー、ブラウザ専用エディタのような「検索に出さないUI」に絞るのが安全です。

ルート単位・ページ単位の分割

コード分割はコンポーネントだけで考えると細かくなりすぎます。まずはルート単位で分けるのが自然です。Next.jsならapp/reports/page.tsxapp/settings/page.tsxのようにページを分けるだけでも、初期表示に不要なページコードを避けやすくなります。React Routerを使うSPAなら、ルートコンポーネントをlazyにします。

// src/AppRouter.tsx
import { Suspense, lazy } from "react";
import { BrowserRouter, Link, Route, Routes } from "react-router-dom";

const HomePage = lazy(() => import("./pages/HomePage"));
const ReportsPage = lazy(() => import("./pages/ReportsPage"));
const SettingsPage = lazy(() => import("./pages/SettingsPage"));

function RouteFallback() {
  return <p aria-live="polite">ページを読み込んでいます...</p>;
}

export default function AppRouter() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/reports">Reports</Link>
        <Link to="/settings">Settings</Link>
      </nav>
      <Suspense fallback={<RouteFallback />}>
        <Routes>
          <Route path="/" element={<HomePage />} />
          <Route path="/reports" element={<ReportsPage />} />
          <Route path="/settings" element={<SettingsPage />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

このとき、すべてのページを一つの巨大なSuspenseで包むと、遅い部品に引きずられて画面全体がローディングになります。ファーストビュー、見出し、CTAは通常表示し、重い部品だけを局所的なSuspenseで包むほうがユーザー体験は安定します。

3つ以上の実務ユースケース

1つ目は、SaaSの管理ダッシュボードです。一般ユーザーが毎回使うホーム画面と、管理者だけが見る売上グラフ、監査ログ、CSV出力を分けます。グラフライブラリや表コンポーネントは重くなりがちなので、権限確認後に読み込む設計が向いています。

2つ目は、ブログや教材サイトの編集画面です。公開記事の本文、見出し、CTAはすぐ表示し、Markdownエディタ、プレビュー、画像トリミングだけを遅延読み込みします。ClaudeCodeLabのように記事と商品導線を扱うサイトでは、読者向けページを軽く保ち、編集者向け機能を別chunkへ逃がすほうが自然です。

3つ目は、地図、グラフ、動画プレーヤーを含むランディングページです。ファーストビューで価値提案とCTAを見せ、下部にスクロールした段階で地図や動画を読み込みます。動画UIを扱う場合はClaude Code動画プレーヤー、キーボード操作や読み上げはアクセシビリティ実装も一緒に確認します。

4つ目は、購入前の見積もりモーダルやステップフォームです。初期表示ではボタンだけ出し、クリック後にフォーム本体を読み込みます。ただし、初回クリックから表示まで1秒以上かかるなら、ボタンが見えた時点で事前読み込みする、フォームを軽くする、入力項目を減らす、といった見直しが必要です。

よくある落とし穴

落とし穴1は、細かく分けすぎることです。小さなchunkが増えすぎると、ネットワーク待ちが増えて逆に遅く感じます。Claude Codeには「初期bundleから何KB減ったか」だけでなく、「追加リクエスト数」「ユーザー操作後の待ち時間」も確認させます。

落とし穴2は、lazydynamicをコンポーネントの内側で宣言することです。React公式ドキュメントでも、lazyコンポーネントはトップレベルで宣言する考え方が示されています。内側に置くと、状態リセット、プリロード不能、意図しない再読み込みにつながります。

落とし穴3は、Suspenseのfallbackが雑なことです。「Loading…」だけだと、どの領域が読み込み中なのか分かりません。高さを確保し、aria-liveを付け、読者が次に待つ理由を理解できる短い文言にします。

落とし穴4は、hydration mismatchです。サーバーでは「未ログイン」、クライアントでは「ログイン済み」、サーバーでは現在時刻A、クライアントでは時刻Bのように表示がズレると起きます。ブラウザ専用の値はuseEffect後に読む、またはClient Componentへ閉じ込めます。

落とし穴5は、SEOや収益導線まで遅延読み込みしてしまうことです。記事タイトル、description相当の本文、価格、CTA、FAQをssr: falseの中へ入れると、検索意図にも読者体験にも悪影響があります。収益に近いUIほど、遅延読み込みの対象から外す判断が必要です。

Claude Codeレビュー用チェックリスト

Claude Codeへのレビュー依頼:
- lazy/dynamicの宣言がモジュールトップレベルにあるか確認して
- default exportとnamed exportの扱いを確認して
- Suspense fallbackが広すぎないか確認して
- ssr:falseがSEOに必要な本文やCTAへ使われていないか確認して
- hydration mismatchになりそうなwindow/date/random/localStorage参照を探して
- 初期JS、遅延chunk、追加リクエスト数を比較する確認手順を書いて

ローカルでは、少なくとも次を確認します。プロジェクトにテストや分析スクリプトがあれば、それも追加します。

npm run lint
npm run build

ブラウザではDevToolsのNetworkタブを開き、JSで絞り込みます。最初のロードで管理画面やエディタのchunkが落ちていないこと、ボタンを押したときにだけ該当chunkが読み込まれること、ローディングUIがレイアウトを押し広げないことを見ます。Lighthouseだけでなく、実際の操作で待ち時間を確認するのが重要です。

公式リンクと学習導線

公式情報は、ReactのlazyReactのSuspenseNext.jsのLazy LoadingNext.jsのLayouts and Pagesを確認してください。細かい実装をClaude Codeに任せる場合でも、これらの前提をプロンプトに入れるとレビューしやすくなります。

個人で始めるなら、まず無料Claude Codeチートシートで基本コマンドと安全な頼み方を固めるのが近道です。毎回同じレビュー観点を書くのが面倒なら、50個のClaude Codeプロンプトテンプレートで、実装、レビュー、パフォーマンス検証の型を増やせます。チームで既存Next.jsアプリの速度改善、CLAUDE.md整備、レビュー運用まで決めたい場合は、Claude Code研修・相談で実リポジトリ前提の進め方を相談できます。

実際に試した結果

この記事の構成で、重いレポートUIとエディタを初期表示から外す手順を検証しました。効果が出たのは、コードを書く前に「ファーストビューとCTAは残す」「重い依存だけ分ける」「Networkでchunkを確認する」と決めた場合です。逆に、Claude Codeへ単に「遅延読み込みして」と頼んだ場合は、Suspenseの範囲が広すぎたり、ssr: falseが不要な場所へ入ったりしました。コード分割は魔法ではなく、読者が最初に必要とする情報を守るための設計作業として扱うほうが、実務では安定します。

#Claude Code #コード分割 #パフォーマンス #React #Next.js
無料

無料PDF: Claude Code はじめてのチートシート

まずは無料PDFで基本コマンドと最初の使い方をまとめて確認してください。登録後はそのままテンプレート集や導入相談にも進めます。

スパムは送りません。登録情報は厳重に管理します。

Claude Codeを仕事で使える形にしませんか?

無料PDFで基礎を固めたあと、すぐ使えるテンプレート集で試し、必要なら業務自動化や導入相談まで進められます。

Masa

この記事を書いた人

Masa

Claude Codeの実務活用、導入設計、収益導線改善を検証しているエンジニア。10言語の技術メディアを運営中。

PR

関連書籍・参考図書

この記事のテーマに関連する書籍を楽天ブックスで探せます。

※ 当サイトは楽天市場のアフィリエイトプログラムに参加しています。上記リンクから商品をご購入いただくと、運営者に紹介料が支払われる場合があります。