路由定义
TanStack Router 提供多种路由定义方式,覆盖从简单静态页到复杂动态路由的所有场景。
基础路由类型
根路由(Root Route)
// src/routes/__root.tsx
import { createRootRoute, Outlet } from '@tanstack/react-router'
export const Route = createRootRoute({
component: () => (
<div>
<Header />
<Outlet /> {/* 子路由在此渲染 */}
<Footer />
</div>
),
notFoundComponent: () => <div>404 - 页面未找到</div>,
errorComponent: ({ error }) => <div>出错了:{error.message}</div>,
pendingComponent: () => <div>加载中...</div>,
})根路由特性:
- 没有路径——它不与任何 URL 匹配
- 始终渲染——所有子路由都在根路由的
<Outlet />中渲染 - 可以定义全局的
notFoundComponent、errorComponent、pendingComponent
普通路由
// src/routes/about.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/about')({
component: AboutPage,
})索引路由(Index Route)
index.tsx 是目录的默认路由,当 URL 精确匹配父路径时渲染:
// src/routes/index.tsx → /
export const Route = createFileRoute('/')({
component: HomePage,
})
// src/routes/posts/index.tsx → /posts
export const Route = createFileRoute('/posts/')({
component: PostsList,
})动态路由参数
使用 $ 前缀定义动态路径段:
// src/routes/posts/$postId.tsx
// 匹配:/posts/123、/posts/hello-world
// 不匹配:/posts、/posts/123/comments
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/$postId')({
component: PostDetail,
// 可选:参数解析(默认都是 string)
params: {
parse: (params) => ({ postId: Number(params.postId) }),
stringify: ({ postId }) => ({ postId: String(postId) }),
},
})使用参数
function PostDetail() {
const { postId } = Route.useParams() // 类型自动推断
return <div>文章 ID: {postId}</div>
}Catch-All / Splat 路由
// src/routes/$.tsx
// 匹配所有未被其他路由匹配的路径
export const Route = createFileRoute('/$')({
component: CatchAll,
})
function CatchAll() {
const { _splat } = Route.useParams() // _splat: string
return <div>未匹配路径: {_splat}</div>
}路径无关布局(Pathless Layout)
以 _ 开头的文件/目录是路径无关的,不会出现在 URL 中:
// src/routes/_authenticated.tsx
import { createFileRoute, Outlet, redirect } from '@tanstack/react-router'
export const Route = createFileRoute('/_authenticated')({
beforeLoad: ({ context, location }) => {
if (!context.auth.isAuthenticated) {
throw redirect({
to: '/login',
search: { redirect: location.href },
})
}
},
component: () => (
<div>
<Sidebar />
<Outlet />
</div>
),
})文件结构 URL 结构
routes/
├── _authenticated.tsx ← 路径无关(守卫 + 布局)
├── _authenticated/
│ ├── dashboard.tsx → /dashboard
│ └── settings.tsx → /settings
└── _authenticated/_admin.tsx ← 双重嵌套路径无关
└── users.tsx → /admin/users ❌
→ /users ✅🚨 陷阱:路径无关布局可以嵌套,但它们都不会出现在 URL 中。
_authenticated/_admin/users.tsx的 URL 是/users,不是/admin/users。
路由配置选项完整列表
export const Route = createFileRoute('/example')({
// === 渲染 ===
component: ExampleComponent, // 路由匹配成功时渲染
// === 数据加载 ===
loader: async ({ params, context }) => {
return fetchData(params.id)
},
beforeLoad: ({ context, location, search }) => {
// 在 loader 之前执行,可用于守卫和重定向
},
loaderDeps: ({ search }) => ({ // 控制 loader 何时重新执行
page: search.page,
}),
// === 搜索参数 ===
validateSearch: (search) => { // 验证并解析搜索参数
return { page: Number(search.page) || 1 }
},
// === 参数解析 ===
params: {
parse: (params) => ({ id: Number(params.id) }),
stringify: ({ id }) => ({ id: String(id) }),
},
// === 错误与加载状态 ===
errorComponent: ErrorDisplay, // loader 失败时渲染
pendingComponent: LoadingSpinner, // loader 执行中渲染
notFoundComponent: NotFound, // 资源不存在时渲染
// === 元数据 ===
staleTime: 30_000, // loader 缓存的过期时间(ms)
preloadStaleTime: 60_000, // 预加载缓存的过期时间(ms)
// === 静态数据 ===
staticData: { // 附加到路由的任意静态数据
title: '示例页面',
breadcrumb: '示例',
},
})虚拟路由(Virtual Routes)
当 .lazy.tsx 文件存在且主路由文件只剩下配置时,可以删除主路由文件——Vite 插件会生成虚拟路由:
// src/routes/posts/$postId.lazy.tsx
// 主文件 src/routes/posts/$postId.tsx 已删除
// Vite 插件自动生成包含 loader/params 的基本路由配置
import { createLazyFileRoute } from '@tanstack/react-router'
export const Route = createLazyFileRoute('/posts/$postId')({
component: PostDetail,
})💡 最佳实践:对于纯展示页面(不需要 loader、beforeLoad、params 解析),直接用虚拟路由可以减少文件数量。
条件路由 / 动态路由注册
代码路由模式支持运行时动态注册:
// 根据权限动态注册管理路由
const adminRoutes = user.hasPermission('admin')
? [
createRoute({
getParentRoute: () => authenticatedRoute,
path: '/admin',
component: AdminPanel,
}),
]
: []
const routeTree = rootRoute.addChildren([
indexRoute,
...adminRoutes,
publicRoute,
])💡 最佳实践:大多数场景下文件路由已足够。仅当需要运行时条件路由或微前端场景时才使用代码路由动态注册。
常见路由模式
| 场景 | 文件路径 | 匹配 URL |
|---|---|---|
| 首页 | routes/index.tsx |
/ |
| 静态页 | routes/about.tsx |
/about |
| 带参数 | routes/posts/$postId.tsx |
/posts/123 |
| 可选参数 | routes/docs/$section.tsx + 搭配 validateSearch |
/docs/intro |
| 嵌套资源 | routes/posts/$postId/comments/$commentId.tsx |
/posts/5/comments/42 |
| 路径无关布局 | routes/_auth.tsx |
不改变 URL |
| Catch-all | routes/$.tsx |
任意未匹配路径 |
🚨 陷阱:TanStack Router 不支持可选路径参数(如
/:param?)。需要可选参数时,用搜索参数(search params)代替。