code-splitting について
TanStack Router の開発者向けに、重要なルートコードと遅延読み込み可能なコンポーネントの分割方法を説明するスキルです。自動・手動の分割と、分割を損なわないインポート方法を扱います。
主な機能
- 01自動および手動のコード分割
- 02重要なルートコードと遅延読み込みコンポーネントの分離
- 03型付きフック用の getRouteApi
- 04ルートごとの分割グループ設定
- 05エクスポートしたコンポーネントのメインバンドル混入防止
活用シーン
- 01ルートコンポーネントの遅延読み込みを設定
- 02バンドラープラグインなしでルートを手動分割
- 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 をインポートすると、コード分割の効果が失われます。
メインバンドルに残るもの(クリティカル)
- パスの解析/シリアライズ
validateSearchloader、beforeLoad- ルートコンテキスト、静的データ
- リンク、スクリプト、スタイル
分割されるもの(非クリティカル)
componenterrorComponentpendingComponentnotFoundComponent
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
},
},
}),
],
})
優先順位
- ルートごとの
codeSplitGroupings(最優先) splitBehavior関数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を使います。