react-router

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

React Router (
@tanstack/react-router
)

React Router (
@tanstack/react-router
)

This 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 confuse
@tanstack/react-router
with
react-router-dom
/
react-router
. They are completely different libraries with different APIs.
本技能基于router-core构建。请先阅读router-core了解基础概念。
本技能涵盖TanStack Router专属React的绑定、组件、钩子和配置方法。
重要提示:TanStack Router的类型是完全自动推导的。永远不要强制类型转换,也不要给推导出来的值添加类型注解。 重要提示:TanStack Router是客户端优先的路由方案。Loader默认在客户端运行,而非服务端。 重要提示:不要将
@tanstack/react-router
react-router-dom
/
react-router
混淆。它们是完全不同的库,API也完全不同。

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-devtools
bash
npm install @tanstack/react-router
npm install -D @tanstack/router-plugin @tanstack/react-router-devtools

2. 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-router
导入。

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()

Subscribe to router state changes. Exposes the entire state and thus incurs a performance cost. For matches or location favor
useMatches
and
useLocation
.
tsx
import { useRouterState } from '@tanstack/react-router'

function LoadingIndicator() {
  const isLoading = useRouterState({ select: (s) => s.isLoading })
  return isLoading ? <div>Loading...</div> : null
}
订阅路由状态变化。会暴露完整状态,因此存在性能开销。如果只需要匹配信息或位置信息,优先使用
useMatches
useLocation
tsx
import { useRouterState } from '@tanstack/react-router'

function LoadingIndicator() {
  const isLoading = useRouterState({ select: (s) => s.isLoading })
  return isLoading ? <div>加载中...</div> : null
}

useNavigate()

useNavigate()

Programmatic navigation (prefer
<Link>
for user-clickable elements):
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 })

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 })

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 })

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 })

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
:
  • useMatches()
    — array of all active route matches (useful for breadcrumbs)
  • useRouteContext({ from })
    — read context from
    beforeLoad
    or parent routes
  • useBlocker({ shouldBlockFn })
    — block navigation for unsaved changes
  • useCanGoBack()
    — returns
    boolean
    , check if history has entries to go back to
  • useLocation()
    — current parsed location (
    pathname
    ,
    search
    ,
    hash
    )
  • useLinkProps({ to, params?, search? })
    — get
    <a>
    props for custom link elements
  • useMatchRoute()
    — returns a function:
    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

RouterProvider

Mount the router at the top of your React tree:
tsx
<RouterProvider router={router} />
在React应用根节点挂载路由:
tsx
<RouterProvider router={router} />

Link

Link

Type-safe navigation link with
<a>
semantics:
tsx
<Link to="/posts/$postId" params={{ postId: '42' }}>
  View Post
</Link>
类型安全的导航链接,具备
<a>
标签语义:
tsx
<Link to="/posts/$postId" params={{ postId: '42' }}>
  查看文章
</Link>

Outlet

Outlet

Renders 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

Navigate

Declarative 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

Await

Renders 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

CatchBoundary

Error boundary for component-level error handling (route-level errors use
errorComponent
route option):
tsx
import { CatchBoundary } from '@tanstack/react-router'
;<CatchBoundary
  getResetKey={() => 'widget'}
  onCatch={(error) => console.error(error)}
  errorComponent={({ error }) => <div>Error: {error.message}</div>}
>
  <RiskyWidget />
</CatchBoundary>
组件级错误处理的错误边界(路由级错误使用
errorComponent
路由配置项):
tsx
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
自定义链接组件

Wrap
Link
in a custom component while preserving type safety:
tsx
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>
  )
}
在保留类型安全的前提下封装
Link
为自定义组件:
tsx
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
from
prop:
tsx
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
strict: false
(which returns an imprecise union) while keeping the component reusable across specific known routes.
如果要创建跨多路由使用路由Hooks的组件,可以将路由路径的联合类型作为
from
属性传入:
tsx
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: false
(会返回不精确的联合类型),同时保持了组件在指定已知路由中的可复用性。

Auth Provider Must Wrap RouterProvider

认证Provider必须包裹RouterProvider

If routes use auth context (via
createRootRouteWithContext
), the auth provider must be an ancestor of
RouterProvider
:
tsx
// 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
Wrap
router option to provide context without wrapping externally:
tsx
const router = createRouter({
  routeTree,
  Wrap: ({ children }) => (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  ),
})
如果路由使用了认证上下文(通过
createRootRouteWithContext
),认证Provider必须是
RouterProvider
的祖先节点:
tsx
// 正确 — AuthProvider包裹RouterProvider
function App() {
  return (
    <AuthProvider>
      <RouterProvider router={router} />
    </AuthProvider>
  )
}

// 错误 — RouterProvider在认证Provider外层
function App() {
  return (
    <RouterProvider router={router}>
      <AuthProvider>{/* ... */}</AuthProvider>
    </RouterProvider>
  )
}
也可以使用路由的
Wrap
配置项提供上下文,无需外层包裹:
tsx
const router = createRouter({
  routeTree,
  Wrap: ({ children }) => (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  ),
})

Common Mistakes

常见错误

1. HIGH: Using React hooks in
beforeLoad
or
loader

1. 严重:在
beforeLoad
loader
中使用React Hooks

beforeLoad
and
loader
are NOT React components — they are plain async functions. React hooks cannot be called in them. Pass auth state via router context instead.
tsx
// 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' })
  }
}
beforeLoad
loader
不是React组件 — 它们是普通的异步函数,不能在其中调用React Hooks。请通过路由上下文传递认证状态。
tsx
// 错误 — 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
undefined!
placeholder, then inject live auth via
RouterProvider
's
context
prop. Do NOT recreate the router on auth changes — this resets caches and rebuilds the tree.
tsx
// 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!
占位符一次性创建路由实例,然后通过
RouterProvider
context
属性注入实时认证状态。不要在认证状态变化时重新创建路由实例 — 这会重置缓存并重建整个路由树。
tsx
// 正确 — 一次性创建路由,通过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

3. 中等:
Await
/延迟数据缺少Suspense边界

Await
requires a
<Suspense>
ancestor. Without it, the deferred promise has no fallback UI and throws.
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>
祖先节点。没有的话,延迟Promise没有兜底UI,会抛出错误。
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等)