react-router
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReact Router (@tanstack/react-router
)
@tanstack/react-routerReact Router (@tanstack/react-router
)
@tanstack/react-routerThis skill builds on router-core. Read router-core first for foundational concepts.
This skill covers the React-specific bindings, components, hooks, and setup for TanStack Router.
CRITICAL: TanStack Router types are FULLY INFERRED. Never cast, never annotate inferred values. CRITICAL: TanStack Router is CLIENT-FIRST. Loaders run on the client by default, not on the server. CRITICAL: Do not confusewith@tanstack/react-router/react-router-dom. They are completely different libraries with different APIs.react-router
本技能基于router-core构建。请先阅读router-core了解基础概念。
本技能涵盖TanStack Router专属React的绑定、组件、钩子和配置方法。
重要提示:TanStack Router的类型是完全自动推导的。永远不要强制类型转换,也不要给推导出来的值添加类型注解。 重要提示:TanStack Router是客户端优先的路由方案。Loader默认在客户端运行,而非服务端。 重要提示:不要将与@tanstack/react-router/react-router-dom混淆。它们是完全不同的库,API也完全不同。react-router
Full Setup: File-Based Routing with Vite
完整配置:基于Vite的文件路由
1. Install Dependencies
1. 安装依赖
bash
npm install @tanstack/react-router
npm install -D @tanstack/router-plugin @tanstack/react-router-devtoolsbash
npm install @tanstack/react-router
npm install -D @tanstack/router-plugin @tanstack/react-router-devtools2. Configure Vite Plugin
2. 配置Vite插件
ts
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { tanstackRouter } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [
// MUST come before react()
tanstackRouter({
target: 'react',
autoCodeSplitting: true,
}),
react(),
],
})ts
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { tanstackRouter } from '@tanstack/router-plugin/vite'
export default defineConfig({
plugins: [
// 必须放在react()之前
tanstackRouter({
target: 'react',
autoCodeSplitting: true,
}),
react(),
],
})3. Create Root Route
3. 创建根路由
tsx
// src/routes/__root.tsx
import { createRootRoute, Link, Outlet } from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
export const Route = createRootRoute({
component: RootLayout,
})
function RootLayout() {
return (
<>
<nav>
<Link to="/" className="[&.active]:font-bold">
Home
</Link>
<Link to="/about" className="[&.active]:font-bold">
About
</Link>
</nav>
<hr />
<Outlet />
<TanStackRouterDevtools />
</>
)
}tsx
// src/routes/__root.tsx
import { createRootRoute, Link, Outlet } from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'
export const Route = createRootRoute({
component: RootLayout,
})
function RootLayout() {
return (
<>
<nav>
<Link to="/" className="[&.active]:font-bold">
首页
</Link>
<Link to="/about" className="[&.active]:font-bold">
关于
</Link>
</nav>
<hr />
<Outlet />
<TanStackRouterDevtools />
</>
)
}4. Create Route Files
4. 创建路由文件
tsx
// src/routes/index.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/')({
component: HomePage,
})
function HomePage() {
return <h1>Welcome Home</h1>
}tsx
// src/routes/about.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/about')({
component: AboutPage,
})
function AboutPage() {
return <h1>About</h1>
}tsx
// src/routes/index.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/')({
component: HomePage,
})
function HomePage() {
return <h1>欢迎来到首页</h1>
}tsx
// src/routes/about.tsx
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/about')({
component: AboutPage,
})
function AboutPage() {
return <h1>关于页面</h1>
}5. Create Router Instance and Register Types
5. 创建路由实例并注册类型
tsx
// src/main.tsx
import { StrictMode } from 'react'
import ReactDOM from 'react-dom/client'
import { RouterProvider, createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
const router = createRouter({ routeTree })
// REQUIRED — without this, Link/useNavigate/useSearch have no type safety
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
const rootElement = document.getElementById('root')!
if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement)
root.render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,
)
}tsx
// src/main.tsx
import { StrictMode } from 'react'
import ReactDOM from 'react-dom/client'
import { RouterProvider, createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
const router = createRouter({ routeTree })
// 必填 — 缺少这一步的话,Link/useNavigate/useSearch将没有类型安全保障
declare module '@tanstack/react-router' {
interface Register {
router: typeof router
}
}
const rootElement = document.getElementById('root')!
if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement)
root.render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>,
)
}Hooks Reference
Hooks参考
All hooks are imported from .
@tanstack/react-router所有钩子都从导入。
@tanstack/react-routeruseRouter()
useRouter()useRouter()
useRouter()Access the router instance directly:
tsx
import { useRouter } from '@tanstack/react-router'
function InvalidateButton() {
const router = useRouter()
return <button onClick={() => router.invalidate()}>Refresh data</button>
}直接访问路由实例:
tsx
import { useRouter } from '@tanstack/react-router'
function InvalidateButton() {
const router = useRouter()
return <button onClick={() => router.invalidate()}>刷新数据</button>
}useRouterState()
useRouterState()useRouterState()
useRouterState()Subscribe to router state changes. Exposes the entire state and thus incurs
a performance cost. For matches or location favor and .
useMatchesuseLocationtsx
import { useRouterState } from '@tanstack/react-router'
function LoadingIndicator() {
const isLoading = useRouterState({ select: (s) => s.isLoading })
return isLoading ? <div>Loading...</div> : null
}订阅路由状态变化。会暴露完整状态,因此存在性能开销。如果只需要匹配信息或位置信息,优先使用和。
useMatchesuseLocationtsx
import { useRouterState } from '@tanstack/react-router'
function LoadingIndicator() {
const isLoading = useRouterState({ select: (s) => s.isLoading })
return isLoading ? <div>加载中...</div> : null
}useNavigate()
useNavigate()useNavigate()
useNavigate()Programmatic navigation (prefer for user-clickable elements):
<Link>tsx
import { useNavigate } from '@tanstack/react-router'
function AfterSubmit() {
const navigate = useNavigate()
const handleSubmit = async () => {
await saveData()
navigate({ to: '/posts/$postId', params: { postId: '123' } })
}
return <button onClick={handleSubmit}>Save</button>
}编程式导航(用户可点击的元素优先使用):
<Link>tsx
import { useNavigate } from '@tanstack/react-router'
function AfterSubmit() {
const navigate = useNavigate()
const handleSubmit = async () => {
await saveData()
navigate({ to: '/posts/$postId', params: { postId: '123' } })
}
return <button onClick={handleSubmit}>保存</button>
}useSearch({ from })
useSearch({ from })useSearch({ from })
useSearch({ from })Read validated search params:
tsx
import { useSearch } from '@tanstack/react-router'
function Pagination() {
const { page } = useSearch({ from: '/products' })
return <span>Page {page}</span>
}读取经过校验的搜索参数:
tsx
import { useSearch } from '@tanstack/react-router'
function Pagination() {
const { page } = useSearch({ from: '/products' })
return <span>第 {page} 页</span>
}useParams({ from })
useParams({ from })useParams({ from })
useParams({ from })Read path params:
tsx
import { useParams } from '@tanstack/react-router'
function PostHeader() {
const { postId } = useParams({ from: '/posts/$postId' })
return <h2>Post {postId}</h2>
}读取路径参数:
tsx
import { useParams } from '@tanstack/react-router'
function PostHeader() {
const { postId } = useParams({ from: '/posts/$postId' })
return <h2>文章 {postId}</h2>
}useLoaderData({ from })
useLoaderData({ from })useLoaderData({ from })
useLoaderData({ from })Read data returned from the route loader:
tsx
import { useLoaderData } from '@tanstack/react-router'
function PostContent() {
const { post } = useLoaderData({ from: '/posts/$postId' })
return <article>{post.content}</article>
}读取路由Loader返回的数据:
tsx
import { useLoaderData } from '@tanstack/react-router'
function PostContent() {
const { post } = useLoaderData({ from: '/posts/$postId' })
return <article>{post.content}</article>
}useMatch({ from })
useMatch({ from })useMatch({ from })
useMatch({ from })Access the full route match (params, search, loader data, context):
tsx
import { useMatch } from '@tanstack/react-router'
function PostDetails() {
const match = useMatch({ from: '/posts/$postId' })
return <div>{match.loaderData.post.title}</div>
}访问完整的路由匹配信息(参数、搜索参数、Loader数据、上下文):
tsx
import { useMatch } from '@tanstack/react-router'
function PostDetails() {
const match = useMatch({ from: '/posts/$postId' })
return <div>{match.loaderData.post.title}</div>
}Other Hooks
其他Hooks
All imported from :
@tanstack/react-router- — array of all active route matches (useful for breadcrumbs)
useMatches() - — read context from
useRouteContext({ from })or parent routesbeforeLoad - — block navigation for unsaved changes
useBlocker({ shouldBlockFn }) - — returns
useCanGoBack(), check if history has entries to go back toboolean - — current parsed location (
useLocation(),pathname,search)hash - — get
useLinkProps({ to, params?, search? })props for custom link elements<a> - — returns a function:
useMatchRoute()matchRoute({ to }) => match | false
全部从导入:
@tanstack/react-router- — 所有激活路由的匹配数组(适用于面包屑场景)
useMatches() - — 读取
useRouteContext({ from })或父路由的上下文beforeLoad - — 阻止导航,适用于未保存变更的场景
useBlocker({ shouldBlockFn }) - — 返回布尔值,检查历史记录是否有可返回的条目
useCanGoBack() - — 当前解析后的位置信息(
useLocation()、pathname、search)hash - — 为自定义链接组件获取
useLinkProps({ to, params?, search? })标签属性<a> - — 返回一个函数:
useMatchRoute()matchRoute({ to }) => match | false
Components Reference
组件参考
RouterProvider
RouterProviderRouterProvider
RouterProviderMount the router at the top of your React tree:
tsx
<RouterProvider router={router} />在React应用根节点挂载路由:
tsx
<RouterProvider router={router} />Link
LinkLink
LinkType-safe navigation link with semantics:
<a>tsx
<Link to="/posts/$postId" params={{ postId: '42' }}>
View Post
</Link>类型安全的导航链接,具备标签语义:
<a>tsx
<Link to="/posts/$postId" params={{ postId: '42' }}>
查看文章
</Link>Outlet
OutletOutlet
OutletRenders the matched child route component:
tsx
function Layout() {
return (
<div>
<Sidebar />
<main>
<Outlet />
</main>
</div>
)
}渲染匹配到的子路由组件:
tsx
function Layout() {
return (
<div>
<Sidebar />
<main>
<Outlet />
</main>
</div>
)
}Navigate
NavigateNavigate
NavigateDeclarative redirect component:
tsx
import { Navigate } from '@tanstack/react-router'
function OldPage() {
return <Navigate to="/new-page" />
}声明式重定向组件:
tsx
import { Navigate } from '@tanstack/react-router'
function OldPage() {
return <Navigate to="/new-page" />
}Await
AwaitAwait
AwaitRenders deferred data from unawaited loader promises with Suspense:
tsx
import { Await } from '@tanstack/react-router'
import { Suspense } from 'react'
function PostWithComments() {
const { deferredComments } = Route.useLoaderData()
return (
<div>
<h1>Post</h1>
<Suspense fallback={<div>Loading comments...</div>}>
<Await promise={deferredComments}>
{(comments) => (
<ul>
{comments.map((c) => (
<li key={c.id}>{c.text}</li>
))}
</ul>
)}
</Await>
</Suspense>
</div>
)
}结合Suspense渲染Loader返回的未等待Promise的延迟数据:
tsx
import { Await } from '@tanstack/react-router'
import { Suspense } from 'react'
function PostWithComments() {
const { deferredComments } = Route.useLoaderData()
return (
<div>
<h1>文章内容</h1>
<Suspense fallback={<div>评论加载中...</div>}>
<Await promise={deferredComments}>
{(comments) => (
<ul>
{comments.map((c) => (
<li key={c.id}>{c.text}</li>
))}
</ul>
)}
</Await>
</Suspense>
</div>
)
}CatchBoundary
CatchBoundaryCatchBoundary
CatchBoundaryError boundary for component-level error handling (route-level errors use route option):
errorComponenttsx
import { CatchBoundary } from '@tanstack/react-router'
;<CatchBoundary
getResetKey={() => 'widget'}
onCatch={(error) => console.error(error)}
errorComponent={({ error }) => <div>Error: {error.message}</div>}
>
<RiskyWidget />
</CatchBoundary>组件级错误处理的错误边界(路由级错误使用路由配置项):
errorComponenttsx
import { CatchBoundary } from '@tanstack/react-router'
;<CatchBoundary
getResetKey={() => 'widget'}
onCatch={(error) => console.error(error)}
errorComponent={({ error }) => <div>错误:{error.message}</div>}
>
<RiskyWidget />
</CatchBoundary>React-Specific Patterns
React特有模式
Custom Link Component with createLink
createLink使用createLink
自定义链接组件
createLinkWrap in a custom component while preserving type safety:
Linktsx
import { createLink } from '@tanstack/react-router'
import { forwardRef, type ComponentPropsWithoutRef } from 'react'
const StyledLinkComponent = forwardRef<
HTMLAnchorElement,
ComponentPropsWithoutRef<'a'>
>((props, ref) => (
<a ref={ref} {...props} className={`styled-link ${props.className ?? ''}`} />
))
const StyledLink = createLink(StyledLinkComponent)
// Usage — same type-safe props as Link
function Nav() {
return (
<StyledLink to="/posts/$postId" params={{ postId: '42' }}>
Post
</StyledLink>
)
}在保留类型安全的前提下封装为自定义组件:
Linktsx
import { createLink } from '@tanstack/react-router'
import { forwardRef, type ComponentPropsWithoutRef } from 'react'
const StyledLinkComponent = forwardRef<
HTMLAnchorElement,
ComponentPropsWithoutRef<'a'>
>((props, ref) => (
<a ref={ref} {...props} className={`styled-link ${props.className ?? ''}`} />
))
const StyledLink = createLink(StyledLinkComponent)
// 使用方式 — 与Link具备相同的类型安全属性
function Nav() {
return (
<StyledLink to="/posts/$postId" params={{ postId: '42' }}>
文章
</StyledLink>
)
}Reusable Components with Router Hooks
结合路由Hooks的可复用组件
To create a component that uses router hooks across multiple routes, pass a union of route paths as the prop:
fromtsx
function PostIdDisplay({ from }: { from: '/posts/$id' | '/drafts/$id' }) {
const { id } = useParams({ from })
return <span>ID: {id}</span>
}
// Usage in different route components
<PostIdDisplay from="/posts/$id" />
<PostIdDisplay from="/drafts/$id" />This pattern avoids (which returns an imprecise union) while keeping the component reusable across specific known routes.
strict: false如果要创建跨多路由使用路由Hooks的组件,可以将路由路径的联合类型作为属性传入:
fromtsx
function PostIdDisplay({ from }: { from: '/posts/$id' | '/drafts/$id' }) {
const { id } = useParams({ from })
return <span>ID: {id}</span>
}
// 在不同路由组件中使用
<PostIdDisplay from="/posts/$id" />
<PostIdDisplay from="/drafts/$id" />该模式避免了使用(会返回不精确的联合类型),同时保持了组件在指定已知路由中的可复用性。
strict: falseAuth Provider Must Wrap RouterProvider
认证Provider必须包裹RouterProvider
If routes use auth context (via ), the auth provider must be an ancestor of :
createRootRouteWithContextRouterProvidertsx
// CORRECT — AuthProvider wraps RouterProvider
function App() {
return (
<AuthProvider>
<RouterProvider router={router} />
</AuthProvider>
)
}
// WRONG — RouterProvider outside auth provider
function App() {
return (
<RouterProvider router={router}>
<AuthProvider>{/* ... */}</AuthProvider>
</RouterProvider>
)
}Or use the router option to provide context without wrapping externally:
Wraptsx
const router = createRouter({
routeTree,
Wrap: ({ children }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
),
})如果路由使用了认证上下文(通过),认证Provider必须是的祖先节点:
createRootRouteWithContextRouterProvidertsx
// 正确 — AuthProvider包裹RouterProvider
function App() {
return (
<AuthProvider>
<RouterProvider router={router} />
</AuthProvider>
)
}
// 错误 — RouterProvider在认证Provider外层
function App() {
return (
<RouterProvider router={router}>
<AuthProvider>{/* ... */}</AuthProvider>
</RouterProvider>
)
}也可以使用路由的配置项提供上下文,无需外层包裹:
Wraptsx
const router = createRouter({
routeTree,
Wrap: ({ children }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
),
})Common Mistakes
常见错误
1. HIGH: Using React hooks in beforeLoad
or loader
beforeLoadloader1. 严重:在beforeLoad
或loader
中使用React Hooks
beforeLoadloaderbeforeLoadloadertsx
// WRONG — useAuth is a React hook, cannot be called here
beforeLoad: () => {
const auth = useAuth()
if (!auth.user) throw redirect({ to: '/login' })
}
// CORRECT — read auth from router context
beforeLoad: ({ context }) => {
if (!context.auth.isAuthenticated) {
throw redirect({ to: '/login' })
}
}beforeLoadloadertsx
// 错误 — useAuth是React Hook,不能在这里调用
beforeLoad: () => {
const auth = useAuth()
if (!auth.user) throw redirect({ to: '/login' })
}
// 正确 — 从路由上下文读取认证信息
beforeLoad: ({ context }) => {
if (!context.auth.isAuthenticated) {
throw redirect({ to: '/login' })
}
}2. HIGH: Wrapping RouterProvider inside an auth provider incorrectly
2. 严重:错误地将RouterProvider包裹在认证Provider内部
Create the router once with an placeholder, then inject live auth via 's prop. Do NOT recreate the router on auth changes — this resets caches and rebuilds the tree.
undefined!RouterProvidercontexttsx
// CORRECT — create router once, inject live auth via context prop
const router = createRouter({
routeTree,
context: { auth: undefined! }, // placeholder, filled by RouterProvider
})
function InnerApp() {
const auth = useAuth()
return <RouterProvider router={router} context={{ auth }} />
}
function App() {
return (
<AuthProvider>
<InnerApp />
</AuthProvider>
)
}使用占位符一次性创建路由实例,然后通过的属性注入实时认证状态。不要在认证状态变化时重新创建路由实例 — 这会重置缓存并重建整个路由树。
undefined!RouterProvidercontexttsx
// 正确 — 一次性创建路由,通过context属性注入实时认证信息
const router = createRouter({
routeTree,
context: { auth: undefined! }, // 占位符,由RouterProvider填充
})
function InnerApp() {
const auth = useAuth()
return <RouterProvider router={router} context={{ auth }} />
}
function App() {
return (
<AuthProvider>
<InnerApp />
</AuthProvider>
)
}3. MEDIUM: Missing Suspense boundary for Await
/deferred data
Await3. 中等:Await
/延迟数据缺少Suspense边界
AwaitAwait<Suspense>tsx
// WRONG — no Suspense boundary
<Await promise={deferredData}>{(data) => <div>{data}</div>}</Await>
// CORRECT — wrap in Suspense
<Suspense fallback={<div>Loading...</div>}>
<Await promise={deferredData}>{(data) => <div>{data}</div>}</Await>
</Suspense>Await<Suspense>tsx
// 错误 — 没有Suspense边界
<Await promise={deferredData}>{(data) => <div>{data}</div>}</Await>
// 正确 — 用Suspense包裹
<Suspense fallback={<div>加载中...</div>}>
<Await promise={deferredData}>{(data) => <div>{data}</div>}</Await>
</Suspense>Cross-References
交叉参考
- router-core/SKILL.md — all sub-skills for domain-specific patterns (search params, data loading, navigation, auth, SSR, etc.)
- router-core/SKILL.md — 领域特定模式的所有子技能(搜索参数、数据加载、导航、认证、SSR等)