mcpField
C
code-splitting

code-splitting

github.com/tanstack開発15.3k201 回インストール4 日前に更新
リポジトリ

code-splitting について

TanStack Router の開発者向けに、重要なルートコードと遅延読み込み可能なコンポーネントの分割方法を説明するスキルです。自動・手動の分割と、分割を損なわないインポート方法を扱います。

主な機能

  1. 01自動および手動のコード分割
  2. 02重要なルートコードと遅延読み込みコンポーネントの分離
  3. 03型付きフック用の getRouteApi
  4. 04ルートごとの分割グループ設定
  5. 05エクスポートしたコンポーネントのメインバンドル混入防止

活用シーン

  1. 01ルートコンポーネントの遅延読み込みを設定
  2. 02バンドラープラグインなしでルートを手動分割
  3. 03分割ファイルから型付きルートフックを呼び出す

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/tanstack/router --skill code-splitting

インストール後、Claude Code で/code-splittingを入力して読み込まれたか確認します。

詳細

コード分割

TanStack Router は、ルートを照合して読み込みを開始するために必要なクリティカルなコードと、遅延読み込みできる非クリティカルなコードに分離します。バンドラープラグインを使えば自動で分割できます。または、.lazy.tsx ファイルを使って手動で分割できます。

CRITICAL:ルートファイルからコンポーネント関数をexportしないでください。エクスポートされた関数はメインバンドルに含まれ、コード分割が完全に回避されます。

CRITICAL:コード分割されたファイルでは、import { Route } from './route'ではなく、getRouteApi('/path')を使ってください。Route をインポートすると、コード分割の効果が失われます。

メインバンドルに残るもの(クリティカル)

  • パスの解析/シリアライズ
  • validateSearch
  • loader、beforeLoad
  • ルートコンテキスト、静的データ
  • リンク、スクリプト、スタイル

分割されるもの(非クリティカル)

  • component
  • errorComponent
  • pendingComponent
  • notFoundComponent

loaderはデフォルトでは分割されません。すでに非同期であるため、分割すると非同期処理のコストが二重にかかります。まずチャンクを取得し、次にローダーを実行する必要があります。特別な理由がある場合にのみ、ローダーを分割してください。

セットアップ:自動コード分割

バンドラープラグインでautoCodeSplitting: trueを有効にします。これが推奨される方法です。

// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { tanstackRouter } from '@tanstack/router-plugin/vite'

export default defineConfig({
  plugins: [
    // TanStack Router plugin MUST come before the framework plugin
    tanstackRouter({
      autoCodeSplitting: true,
    }),
    react(),
  ],
})

これを有効にすると、ルートファイルが自動的に変換されます。コンポーネントは個別のチャンクに分割され、ローダーはメインバンドルに残ります。.lazy.tsxファイルは不要です。

// src/routes/posts.tsx — everything in one file, splitting is automatic
import { createFileRoute } from '@tanstack/react-router'
import { fetchPosts } from '../api'

export const Route = createFileRoute('/posts')({
  loader: fetchPosts,
  component: PostsComponent,
})

// NOT exported — this is critical for automatic code splitting to work
function PostsComponent() {
  const posts = Route.useLoaderData()
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

.lazy.tsxを使った手動分割

自動コード分割を使えない場合(例:CLI のみで、バンドラープラグインがない場合)は、2 つのファイルに手動で分割します。

// src/routes/posts.tsx — critical route config only
import { createFileRoute } from '@tanstack/react-router'
import { fetchPosts } from '../api'

export const Route = createFileRoute('/posts')({
  loader: fetchPosts,
})
// src/routes/posts.lazy.tsx — non-critical (lazy-loaded)
import { createLazyFileRoute } from '@tanstack/react-router'

export const Route = createLazyFileRoute('/posts')({
  component: PostsComponent,
})

function PostsComponent() {
  // Use getRouteApi to access typed hooks without importing Route
  return <div>Posts</div>
}

createLazyFileRouteがサポートするのは、component、errorComponent、pendingComponent、notFoundComponentのみです。

仮想ルート

分割によってクリティカルなルートファイルが空になった場合は、そのファイルを完全に削除してください。仮想ルートはrouteTree.gen.tsに自動生成されます。

// src/routes/about.lazy.tsx — no about.tsx needed
import { createLazyFileRoute } from '@tanstack/react-router'

export const Route = createLazyFileRoute('/about')({
  component: () => <h1>About Us</h1>,
})

コードベースの分割

コードベース(ファイルベースではない)のルーティングでは、createLazyRouteと.lazy()メソッドを使います。

// src/posts.lazy.tsx
import { createLazyRoute } from '@tanstack/react-router'

export const Route = createLazyRoute('/posts')({
  component: PostsComponent,
})

function PostsComponent() {
  return <div>Posts</div>
}
// src/app.tsx
import { createRoute } from '@tanstack/react-router'

const postsRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/posts',
}).lazy(() => import('./posts.lazy').then((d) => d.Route))

分割ファイルで型付きフックにアクセスする:getRouteApi

コンポーネントが別のファイルにある場合は、Route オブジェクトをインポートせずに型付きでルートフックにアクセスするため、getRouteApiを使います。

// src/routes/posts.lazy.tsx
import { createLazyFileRoute, getRouteApi } from '@tanstack/react-router'

const routeApi = getRouteApi('/posts')

export const Route = createLazyFileRoute('/posts')({
  component: PostsComponent,
})

function PostsComponent() {
  const posts = routeApi.useLoaderData()
  const { page } = routeApi.useSearch()
  const params = routeApi.useParams()
  const context = routeApi.useRouteContext()
  return <div>Posts page {page}</div>
}

getRouteApiが提供するもの:useLoaderData、useLoaderDeps、useMatch、useParams、useRouteContext、useSearch。

ルートごとの分割設定の上書き:codeSplitGroupings

ルートファイルにcodeSplitGroupingsを直接追加すると、特定のルートの分割動作を上書きできます。

// src/routes/posts.tsx
import { createFileRoute } from '@tanstack/react-router'
import { loadPostsData } from './-heavy-posts-utils'

export const Route = createFileRoute('/posts')({
  // Bundle loader and component together for this route
  codeSplitGroupings: [['loader', 'component']],
  loader: () => loadPostsData(),
  component: PostsComponent,
})

function PostsComponent() {
  const data = Route.useLoaderData()
  return <div>{data.title}</div>
}

グローバルな分割設定

defaultBehavior — デフォルトのグループ分けを変更する
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackRouter } from '@tanstack/router-plugin/vite'

export default defineConfig({
  plugins: [
    tanstackRouter({
      autoCodeSplitting: true,
      codeSplittingOptions: {
        defaultBehavior: [
          // Bundle all UI components into one chunk
          [
            'component',
            'pendingComponent',
            'errorComponent',
            'notFoundComponent',
          ],
        ],
      },
    }),
  ],
})
splitBehavior — ルートごとのロジックをプログラムで指定する
// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackRouter } from '@tanstack/router-plugin/vite'

export default defineConfig({
  plugins: [
    tanstackRouter({
      autoCodeSplitting: true,
      codeSplittingOptions: {
        splitBehavior: ({ routeId }) => {
          if (routeId.startsWith('/posts')) {
            return [['loader', 'component']]
          }
          // All other routes use defaultBehavior
        },
      },
    }),
  ],
})
優先順位
  1. ルートごとのcodeSplitGroupings(最優先)
  2. splitBehavior関数
  3. defaultBehaviorオプション(最も低い優先順位)

よくある間違い

1. HIGH:コンポーネント関数をエクスポートするとコード分割されない
// WRONG — export puts PostsComponent in the main bundle
export function PostsComponent() {
  return <div>Posts</div>
}

// CORRECT — no export, function stays in the split chunk
function PostsComponent() {
  return <div>Posts</div>
}
2. MEDIUM:ルートルートをコード分割しようとする

__root.tsxはコード分割をサポートしていません。現在のルートにかかわらず、常にレンダリングされます。__root.lazy.tsxは作成しないでください。

3. MEDIUM:ローダーを分割すると非同期処理のコストが二重にかかる
// AVOID unless you have a specific reason
codeSplittingOptions: {
  defaultBehavior: [
    ['loader'], // Fetch chunk THEN execute loader = two network waterfalls
    ['component'],
  ],
}

// PREFERRED — loader stays in main bundle (default behavior)
codeSplittingOptions: {
  defaultBehavior: [
    ['component'],
    ['errorComponent'],
    ['notFoundComponent'],
  ],
}
4. HIGH:型付きフックを使うためにコード分割ファイルで Route をインポートする
// WRONG — importing Route pulls route config into the lazy chunk
import { Route } from './posts.tsx'
const data = Route.useLoaderData()

// CORRECT — getRouteApi gives typed hooks without pulling in the route
import { getRouteApi } from '@tanstack/react-router'
const routeApi = getRouteApi('/posts')
const data = routeApi.useLoaderData()

関連項目

  • router-core/data-loading — ローダーの分割に関する判断は、データ読み込みのパフォーマンスに影響します。ローダーを分割すると、データを取得できるようになるまでの遅延が増加します。
  • router-core/type-safety — 分割ファイルからフックにアクセスする際は、型安全な方法であるgetRouteApiを使います。

コメント