code-splitting
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCode Splitting
代码分割
TanStack Router separates route code into critical (required to match and start loading) and non-critical (can be lazy-loaded). The bundler plugin can split automatically, or you can split manually with files.
.lazy.tsxCRITICAL: Nevercomponent functions from route files — exported functions are included in the main bundle and bypass code splitting entirely.export
CRITICAL: Usein code-split files, NOTgetRouteApi('/path'). Importing Route defeats code splitting.import { Route } from './route'
TanStack Router 将路由代码分为关键代码(匹配路由和启动加载所必需)和非关键代码(可懒加载)。打包器插件可以自动完成分割,你也可以通过 文件手动分割。
.lazy.tsx重要警告:切勿从路由文件中组件函数——导出的函数会被纳入主包,完全绕过代码分割机制。export
重要警告:在代码分割文件中请使用,而非getRouteApi('/path')。导入 Route 会使代码分割失效。import { Route } from './route'
What Stays in the Main Bundle (Critical)
主包中保留的内容(关键代码)
- Path parsing/serialization
validateSearch- ,
loaderbeforeLoad - Route context, static data
- Links, scripts, styles
- 路径解析/序列化
validateSearch- 、
loaderbeforeLoad - 路由上下文、静态数据
- 链接、脚本、样式
What Gets Split (Non-Critical)
会被分割的内容(非关键代码)
componenterrorComponentpendingComponentnotFoundComponent
Theis NOT split by default. It is already async, so splitting it adds a double async cost: fetch the chunk, then execute the loader. Only split the loader if you have a specific reason.loader
componenterrorComponentpendingComponentnotFoundComponent
默认不会被分割。它本身已经是异步的,因此分割它会带来双重异步开销:先获取代码块,再执行 loader。除非有特殊原因,否则不要分割 loader。loader
Setup: Automatic Code Splitting
配置:自动代码分割
Enable in the bundler plugin. This is the recommended approach.
autoCodeSplitting: truets
// 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(),
],
})With this enabled, route files are automatically transformed. Components are split into separate chunks; loaders stay in the main bundle. No files needed.
.lazy.tsxtsx
// 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>
)
}在打包器插件中启用 ,这是推荐的实现方式。
autoCodeSplitting: truets
// 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(),
],
})启用该配置后,路由文件会被自动转换。组件会被分割到独立的代码块中,loader 则保留在主包里,无需编写 文件。
.lazy.tsxtsx
// 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>
)
}Manual Splitting with .lazy.tsx
.lazy.tsx使用 .lazy.tsx
手动分割
.lazy.tsxIf you cannot use automatic code splitting (e.g. CLI-only, no bundler plugin), split manually into two files:
tsx
// 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,
})tsx
// 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>
}createLazyFileRoutecomponenterrorComponentpendingComponentnotFoundComponent如果你无法使用自动代码分割(例如仅使用 CLI、没有打包器插件),可以手动拆分为两个文件:
tsx
// 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,
})tsx
// 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>
}createLazyFileRoutecomponenterrorComponentpendingComponentnotFoundComponentVirtual Routes
虚拟路由
If splitting leaves the critical route file empty, delete it entirely. A virtual route is auto-generated in :
routeTree.gen.tstsx
// 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>,
})如果分割后关键路由文件为空,可以直接删除它。系统会在 中自动生成虚拟路由:
routeTree.gen.tstsx
// 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>,
})Code-Based Splitting
基于代码的分割
For code-based (non-file-based) routing, use and the method:
createLazyRoute.lazy()tsx
// src/posts.lazy.tsx
import { createLazyRoute } from '@tanstack/react-router'
export const Route = createLazyRoute('/posts')({
component: PostsComponent,
})
function PostsComponent() {
return <div>Posts</div>
}tsx
// src/app.tsx
import { createRoute } from '@tanstack/react-router'
const postsRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/posts',
}).lazy(() => import('./posts.lazy').then((d) => d.Route))对于基于代码(非文件式)的路由,请使用 和 方法:
createLazyRoute.lazy()tsx
// src/posts.lazy.tsx
import { createLazyRoute } from '@tanstack/react-router'
export const Route = createLazyRoute('/posts')({
component: PostsComponent,
})
function PostsComponent() {
return <div>Posts</div>
}tsx
// src/app.tsx
import { createRoute } from '@tanstack/react-router'
const postsRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/posts',
}).lazy(() => import('./posts.lazy').then((d) => d.Route))Accessing Typed Hooks in Split Files: getRouteApi
getRouteApi在分割文件中访问类型化钩子:getRouteApi
getRouteApiWhen your component lives in a separate file, use to get typed access to route hooks without importing the Route object:
getRouteApitsx
// 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>
}getRouteApiuseLoaderDatauseLoaderDepsuseMatchuseParamsuseRouteContextuseSearch当你的组件位于独立文件中时,可以使用 获取路由钩子的类型化访问能力,无需导入 Route 对象:
getRouteApitsx
// 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>
}getRouteApiuseLoaderDatauseLoaderDepsuseMatchuseParamsuseRouteContextuseSearchPer-Route Split Overrides: codeSplitGroupings
codeSplitGroupings单路由分割覆盖:codeSplitGroupings
codeSplitGroupingsOverride split behavior for a specific route by adding directly in the route file:
codeSplitGroupingstsx
// 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>
}直接在路由文件中添加 ,可以覆盖特定路由的分割行为:
codeSplitGroupingstsx
// 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>
}Global Split Configuration
全局分割配置
defaultBehavior
— Change Default Groupings
defaultBehaviordefaultBehavior
— 修改默认分组
defaultBehaviorts
// 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',
],
],
},
}),
],
})ts
// 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
— Programmatic Per-Route Logic
splitBehaviorsplitBehavior
— 编程式单路由逻辑
splitBehaviorts
// 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
},
},
}),
],
})ts
// 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
},
},
}),
],
})Precedence Order
优先级顺序
- Per-route (highest)
codeSplitGroupings - function
splitBehavior - option (lowest)
defaultBehavior
- 单路由 (最高)
codeSplitGroupings - 函数
splitBehavior - 配置项(最低)
defaultBehavior
Common Mistakes
常见错误
1. HIGH: Exporting component functions prevents code splitting
1. 高危:导出组件函数会导致代码分割失效
tsx
// 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>
}tsx
// 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: Trying to code-split the root route
2. 中危:尝试对根路由进行代码分割
__root.tsx__root.lazy.tsx__root.tsx__root.lazy.tsx3. MEDIUM: Splitting the loader adds double async cost
3. 中危:分割 loader 会带来双重异步开销
tsx
// 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'],
],
}tsx
// 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: Importing Route in code-split files for typed hooks
4. 高危:在代码分割文件中导入 Route 以使用类型化钩子
tsx
// 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()tsx
// 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()Cross-References
相关参考
- router-core/data-loading — Loader splitting decisions affect data loading performance. Splitting the loader adds latency before data can be fetched.
- router-core/type-safety — is the type-safe way to access hooks from split files.
getRouteApi
- router-core/data-loading — Loader 分割决策会影响数据加载性能。分割 loader 会增加数据获取前的延迟。
- router-core/type-safety — 是从分割文件中访问钩子的类型安全方式。
getRouteApi