关于 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:在代码拆分文件中使用
getRouteApi('/path'),而非import { Route } from './route'。导入 Route 会破坏代码拆分。
主包中保留的内容(关键代码)
- 路径解析/序列化
validateSearchloader、beforeLoad- 路由上下文、静态数据
- 链接、脚本、样式
拆分的内容(非关键代码)
componenterrorComponentpendingComponentnotFoundComponent
默认情况下不会拆分
loader。它本身已经是异步的,因此拆分会增加双重异步开销:先获取代码块,再执行 loader。只有在有特定原因时才拆分 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(),
],
})
启用后,路由文件会自动转换。组件会拆分到单独的代码块中;loader 则保留在主包中。无需创建 .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,没有打包工具插件),请手动拆分为两个文件:
// 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))
在拆分文件中访问类型化 Hook:getRouteApi
当组件位于单独的文件中时,请使用 getRouteApi,无需导入 Route 对象即可获得对路由 Hook 的类型化访问:
// 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. 高:导出组件函数会阻止代码拆分
// 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. 中:尝试拆分根路由
__root.tsx 不支持代码拆分。无论当前路由是什么,它始终都会渲染。不要创建 __root.lazy.tsx。
3. 中:拆分 loader 会增加双重异步开销
// 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. 高:在代码拆分文件中导入 Route 以访问类型化 Hook
// 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 — loader 拆分决策会影响数据加载性能。拆分 loader 会增加获取数据前的延迟。
- router-core/type-safety —
getRouteApi是从拆分文件访问 Hook 的类型安全方式。