mcpField
T
tanstack-router

tanstack-router

github.com/bm629开发796 次安装2 周前更新
代码仓库

关于 tanstack-router

tanstack-router 是用于在 React SPA 中配置和扩展 TanStack Router 的技能。内容包括类型安全路由、经过验证的 URL 搜索状态、加载器、路由层的 TanStack Query 集成及路由组件测试。

主要功能

  1. 01类型安全的路由定义与导航
  2. 02基于文件和代码的路由
  3. 03经过验证的搜索参数
  4. 04路由加载器与预加载
  5. 05TanStack Query 加载器集成
  6. 06内存路由组件测试

适用场景

  1. 01在 React SPA 中配置路由
  2. 02添加类型化路由和搜索状态
  3. 03使用 TanStack Query 预取路由数据
  4. 04测试依赖路由器的组件

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/bm629/agent-skills --skill tanstack-router

安装后在 Claude Code 中输入/tanstack-router确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
auth-and-testing.md
navigation-and-splitting.md
query-integration.md
routing-approaches.md
search-and-loaders.md
sources.md

详细介绍

tanstack-router — SKILL.md

**变体:**标准 · **使用时机:**调用此技能后,你会阅读它,并连接或扩展 TanStack Router 应用,然后将控制权交还给调用方。

已根据 @tanstack/react-router v1(1.170.x)和截至 2026-06 的 TanStack Router 文档进行验证。 TanStack Router 处于 v1 主版本(持续发布 semver 次版本)。以下 API 已在技能编写时对照官方 tanstack.com/router 文档核验。请将确切的选项名称视为与版本绑定:如果某个名称在你安装的版本中返回 404,请查看 references/sources.md 中列出的该版本文档页面。

概述

TanStack Router 是适用于 React SPA 的完全类型安全的客户端路由器。它的独特之处在于,路由树是类型的来源:通过 TypeScript 声明合并注册路由器后,每个 Link、navigate、useParams、useSearch 和 useLoaderData 都会根据实际路由进行检查——无效路径和缺失参数会导致编译错误。它内置了一流的搜索参数(由模式验证的 URL 状态)、路由加载器(渲染前获取数据,并支持预加载)和代码拆分。此技能会搭建路由器、以类型安全的方式连接路由,并介绍加载器与数据获取之间的衔接(包括 TanStack Query 的协作方式),以及用于路由组件的测试工具。它是路由层;全栈框架 TanStack Start 构建于其之上(不在本文范围内——请参阅边界)。

何时启用

  • ✅ 在 Vite + React + TypeScript SPA 中设置路由(或扩展现有的 TanStack Router 应用)。
  • ✅ 定义路由、类型化导航、经验证的搜索参数或路由加载器。
  • ✅ 将路由加载器连接到 TanStack Query(在加载器中预取,在组件中读取)。
  • ✅ 编写用于渲染路由组件的测试(需要内存历史记录测试工具)。

请勿在以下情况启用:

  • 项目使用 React Router(react-router / Remix)——这是另一个路由器;此技能不适用于它。
  • 需要 TanStack Start SSR / 服务端函数 / 流式反序列化——Start 是下游使用方,本技能不介绍它。
  • 需要脱离路由使用的 TanStack Query 机制(缓存配置、变更、与路由无关的无限查询)——这属于 tanstack-query 技能的职责;此技能仅介绍通过路由加载器/上下文使用查询。

工作流

步骤 1:安装并连接打包工具插件
npm install @tanstack/react-router
npm install -D @tanstack/router-plugin                    # file-based routing (Vite/Rspack/Webpack)
npm install -D @tanstack/react-router-devtools            # optional, dev-only

将插件添加到 Vite。当前导出项是来自 @tanstack/router-plugin/vite 的 tanstackRouter(驼峰命名),必须在 @vitejs/plugin-react 之前运行:

// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackRouter } from '@tanstack/router-plugin/vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [
    tanstackRouter({ target: 'react', autoCodeSplitting: true }),
    react(),
  ],
})

该插件会监视 src/routes/ 并生成 src/routeTree.gen.ts。(旧名称 TanStackRouterVite 和 @tanstack/router-vite-plugin 包仍可通过别名使用,但 tanstackRouter 是当前名称。请参阅 references/sources.md。)

步骤 2:类型安全设置(createRouter + RouterProvider + Register)

这是让整个应用的导航具备类型安全性的连接方式。Register 声明合并是必需的——没有它,类型化钩子会退化为联合类型,并失去自动补全和编译检查。

// src/router.tsx
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'

export const router = createRouter({
  routeTree,
  defaultPreload: 'intent',          // preload on hover/focus; see Step 6
  scrollRestoration: true,
})

// Declaration merging: binds YOUR router's type into the library's hooks.
declare module '@tanstack/react-router' {
  interface Register {
    router: typeof router
  }
}
// src/main.tsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { RouterProvider } from '@tanstack/react-router'
import { router } from './router'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <RouterProvider router={router} />
  </StrictMode>,
)
步骤 3:根路由
// src/routes/__root.tsx
import { createRootRoute, Outlet, Link } from '@tanstack/react-router'
import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'

export const Route = createRootRoute({
  component: () => (
    <>
      <nav>
        <Link to="/">Home</Link>{' '}
        <Link to="/posts">Posts</Link>
      </nav>
      <Outlet />
      <TanStackRouterDevtools />
    </>
  ),
})

__root.tsx 会包装每条路由;<Outlet /> 会渲染匹配的子路由。若要使用类型化路由器上下文(例如注入 queryClient),请改用 createRootRouteWithContext<T>()——请参阅 references/auth-and-testing.md 和 references/query-integration.md。

步骤 4:完整路由示例——基于文件的路由(推荐默认方式)

基于文件的路由是 TanStack 的默认方式,也是此处的主要路径。文件的位置决定其 URL;createFileRoute('/path') 会先接收路径字符串,再接收选项对象。一个包含路径参数、已验证搜索参数、加载器和组件的完整路由:

// src/routes/posts.$postId.tsx        ->  matches /posts/$postId
import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'
import { zodValidator } from '@tanstack/zod-adapter'

const postSearchSchema = z.object({
  tab: z.enum(['body', 'comments']).catch('body'),
})

export const Route = createFileRoute('/posts/$postId')({
  validateSearch: zodValidator(postSearchSchema),   // Zod v3 adapter; Zod v4: pass schema directly
  loader: async ({ params }) => fetchPost(params.postId),
  component: PostComponent,
})

function PostComponent() {
  const { postId } = Route.useParams()        // { postId: string } — typed
  const { tab } = Route.useSearch()           // { tab: 'body' | 'comments' } — typed
  const post = Route.useLoaderData()          // typed to the loader's return
  return <article data-tab={tab}>{post.title}</article>
}

Route.useParams() / Route.useSearch() / Route.useLoaderData() 是文件内的类型化钩子——无需 from 参数,因为路由对象本身就是数据来源。在其他文件中使用 useParams({ from: '/posts/$postId' }) 或 getRouteApi('/posts/$postId')(参见 references/search-and-loaders.md)。

基于代码的路由(通过 getParentRoute + addChildren + 手动 createRouter 使用 createRootRoute / createRoute)也受到全面支持,并在 references/routing-approaches.md 中作为一等方式介绍——当你无法运行打包器插件时,请使用它。

步骤 5:导航要点
import { Link, useNavigate, redirect } from '@tanstack/react-router'

// Declarative — renders a real <a href>; preloads on intent.
<Link to="/posts/$postId" params={{ postId: '123' }} search={{ tab: 'comments' }}
      activeProps={{ className: 'active' }}>Post 123</Link>

// Imperative — for side effects (after a form submit, etc.).
const navigate = useNavigate()
navigate({ to: '/posts', search: (prev) => ({ ...prev, tab: 'body' }) })

// Redirect — THROW it from beforeLoad/loader (never navigate in a component for guards).
throw redirect({ to: '/login', search: { redirect: location.href } })

完整功能——activeOptions、渲染属性 isActive、导航阻止(useBlocker)、路由遮蔽——见 references/navigation-and-splitting.md。

步骤 6:从加载器到数据的衔接(以及 TanStack Query 握手)

路由加载器会在组件渲染之前运行,因此组件挂载时数据已就绪,并可根据链接意图进行预加载。与 TanStack Query 集成的推荐方式:定义一个 queryOptions 工厂,在加载器中调用 ensureQueryData,然后在组件中通过 useSuspenseQuery 读取数据。

import { createFileRoute } from '@tanstack/react-router'
import { useSuspenseQuery } from '@tanstack/react-query'
import { postQueryOptions } from '../queries'   // a queryOptions factory

export const Route = createFileRoute('/posts/$postId')({
  loader: ({ context: { queryClient }, params }) =>
    queryClient.ensureQueryData(postQueryOptions(params.postId)),
  component: Post,
})

function Post() {
  const { postId } = Route.useParams()
  const { data: post } = useSuspenseQuery(postQueryOptions(postId))  // cache hit — no refetch
  return <article>{post.title}</article>
}

queryClient 通过路由器上下文传入加载器(createRootRouteWithContext<{ queryClient: QueryClient }>() + createRouter({ context: { queryClient } }))。完整集成方式——等待与延迟加载、defaultPreloadStaleTime: 0、通过 router.invalidate() 在变更后失效,以及指向规范 tanstack-query 技能的交叉引用——见 references/query-integration.md。此处不会讲解 TanStack Query 自身的机制(抽象层面的查询键、变更、缓存调优);这些内容属于 tanstack-query 技能。

步骤 7:测试路由组件

要在测试中渲染路由,而无需浏览器 URL,请构建一个内存路由器并使用 RouterProvider 包装。本技能负责提供这个测试框架;组件测试技能会使用它。

import { render, screen } from '@testing-library/react'
import { createMemoryHistory, createRouter, RouterProvider, createRootRoute, createRoute } from '@tanstack/react-router'

function renderRoute(initialPath: string) {
  const rootRoute = createRootRoute()
  const indexRoute = createRoute({ getParentRoute: () => rootRoute, path: '/', component: HomePage })
  const routeTree = rootRoute.addChildren([indexRoute])
  const router = createRouter({
    routeTree,
    history: createMemoryHistory({ initialEntries: [initialPath] }),
  })
  render(<RouterProvider router={router} />)
}

使用 Testing Library 的 findBy* 等待渲染结果(路由器会在匹配的组件出现前解析加载器)。通过 router.navigate(...) 驱动导航。完整测试框架——类型化测试路由器工厂、基于文件的路由树变体、等待 router.load()——见 references/auth-and-testing.md。

规则

硬性规则(绝不违反):

  • 始终添加 Register 声明合并。 如果没有 declare module '@tanstack/react-router' { interface Register { router: typeof router } },所有类型化 Hook 都会退化为所有路由的联合类型,你也会失去编译时路由检查。
  • 将身份验证/重定向守卫放在 beforeLoad(或 loader)中,绝不要放在组件中。 从 beforeLoad 抛出 redirect(...) 会在路由渲染前将其拦截;在组件中进行守卫则会先闪现受保护的内容。
  • 只要 loader 依赖搜索参数,就使用 loaderDeps。 除非在 loaderDeps 中声明依赖,否则搜索参数变化时 loader 不会重新运行;省略它会导致提供过期数据。
  • 通过 validateSearch 验证每个搜索参数。 搜索参数是用户可控的 URL 输入;未经验证就读取会留下 any 类型漏洞并带来运行时风险。
  • 在自身路由文件之外使用类型化 Hook 时,传入 from(或使用 Route.*/getRouteApi)。 在共享组件中直接调用 useParams() 会返回联合类型,而不是该路由的精确类型。
  • 通过路由来处理查询机制。 本技能仅介绍 TanStack Query 在 loader/上下文交界处的用法,并将 tanstack-query 作为规范参考;不要将独立的查询模式搬进路由代码。

偏好(可覆盖):

  • 优先采用基于文件的路由(tanstackRouter 插件和 createFileRoute);只有无法运行打包工具插件时才使用基于代码的路由。
  • 用户主动发起导航时,优先使用 <Link>,而不是 useNavigate(真实的 <a href>、鼠标中键打开、预加载、无障碍支持);仅在副作用场景使用 useNavigate。
  • 将 defaultPreload 设为 'intent',实现流畅导航;当缓存由 TanStack Query 管理时,将 defaultPreloadStaleTime 设为 0。
  • 将 loader 保留在初始打包文件中(不要对它进行代码拆分),除非有明确理由;拆分会在获取数据前增加一次服务器往返。

易踩问题

  • TanStackRouterVite/@tanstack/router-vite-plugin 看起来没问题,但它们是旧名称。 当前导出项是来自 @tanstack/router-plugin/vite 的 tanstackRouter。旧名称仍有别名支持,因此代码虽然“能运行”,却会逐渐偏离文档;请使用当前名称。
  • routeTree.gen.ts 是生成文件——绝不要手动编辑,并且必须在 createRouter 之前存在。 如果导入 ./routeTree.gen 失败,说明插件尚未运行(启动开发服务器/构建),或它在 Vite 配置中的顺序排在 react() 之后。
  • loader 无提示地提供过期数据,几乎总是因为缺少 loaderDeps。 除非通过 loaderDeps: ({ search }) => ({ page: search.page }) 声明依赖,否则更改 ?page=2 不会让 loader 重新运行。
  • 共享组件中的 useParams() 类型“失效”。 如果没有 from,它会联合所有路由的参数。请使用 from、Route.useParams()(同一文件)或 getRouteApi(path)(拆分文件)。
  • Zod 适配器版本陷阱。 对于 Zod v3,请使用 @tanstack/zod-adapter 中的 zodValidator 进行包装;对于 Zod v4,则不需要适配器——将 schema 直接传给 validateSearch。旧的 @tanstack/router-zod-adapter 包已弃用。
  • 延迟数据:返回原始 Promise,不要对它使用 await。 当前文档会从 loader 返回一个未等待的 Promise,并通过 <Await>/useAwaited 使用它。独立的 defer() 助手主要用于 SSR/流式序列化(Start),在 SPA 中并非必需——参见 references/search-and-loaders.md。
  • 没有 loader 预取时,useSuspenseQuery 会在首次绘制时挂起。 在 loader 中使用 ensureQueryData 的意义在于,组件的 useSuspenseQuery 可以命中缓存。跳过 loader 就会重新引入 loader 原本要消除的瀑布式请求。

反模式

  • “我会在 useEffect 中获取数据,而不是使用 loader。” 这会再次引入先渲染再获取数据的瀑布流,并使预加载失效——loader 才是关键。
  • “我会在组件内通过重定向来保护路由。” 这会导致受保护内容一闪而过,还会多一次渲染。应在 beforeLoad 中进行保护。
  • “我会跳过 validateSearch,直接读取原始的 useSearch()。” 这是来自 URL、未经验证的 any;URL 格式错误会导致运行时崩溃。
  • “既然要集成 TanStack Query,我就在这里介绍完整的 TanStack Query 缓存/变更 API。” 这会造成范围蔓延——应从路由的角度来讲解,并指向 tanstack-query。
  • “既然路由器支持 TanStack Start 的 SSR/流式传输,我就把它们写进文档。” Start 是独立产品(下游使用方);应将其排除在外。

输出

此技能生成可运行的 TanStack Router 代码:vite.config.ts 插件入口、router.tsx(createRouter + Register)、RouterProvider 接线、路由模块(以基于文件的方式为主,也支持基于代码的方式)、类型化导航和钩子、经过验证的搜索参数、与 TanStack Query 接线的 loader,以及内存历史测试框架。抽象的使用方是在 React SPA 中搭建路由的工程师或编码 Agent,以及复用该测试框架的同级组件测试工作流。

相关内容

  • tanstack-query — TanStack Query 机制(查询键、queryOptions、变更、缓存调优)的权威来源。此技能会在 loader/上下文的交界处引用它,不会重复介绍。
  • vite — 运行 @tanstack/router-plugin/vite 插件并生成 routeTree.gen.ts 的开发服务器和打包工具。
  • react-component-testing(同级)— 使用此技能负责的路由器测试框架(步骤 7/references/auth-and-testing.md)。
  • typescript-typecheck — Register 扩展和类型化钩子依赖严格的 tsconfig;路由树类型检查由 tsc 强制执行。
  • 边界(仅说明名称,不作讲解): TanStack Start(SSR/流式传输全栈框架——下游使用方);React Router(react-router/Remix——此技术栈未选择的替代方案)。

渐进式披露

较大的内容存放在 references/ 中,按需加载:

  • references/routing-approaches.md — 选择或实现路由结构时加载:完整的文件命名约定表(__root、index、$param、_pathless、(group)、$ 通配符、点号表示法、尾随下划线取消嵌套),以及完整的基于代码的实现方式(createRoute + getParentRoute + addChildren + 手动调用 createRouter),并附带使用时机对比。
  • references/search-and-loaders.md — 深入了解搜索参数和数据加载时加载:使用可插拔验证器的 validateSearch(包含 Zod 适配器的完整示例,并介绍 Valibot/自定义验证器)、读取/更新搜索参数、父级→子级继承、自定义序列化、搜索中间件(retainSearchParams/stripSearchParams);加载器、loaderDeps、延迟数据(原始 Promise + Await)、并行加载、上下文加载。
  • references/navigation-and-splitting.md — 处理导航和包体积时加载:Link 选项、useNavigate、redirect、useBlocker、代码拆分(createLazyFileRoute/autoCodeSplitting)、预加载(defaultPreload 的取值、preloadDelay、过期时间)、滚动恢复、路由遮罩。
  • references/auth-and-testing.md — 处理身份验证模式或测试时加载:createRootRouteWithContext、无路径的 _authenticated 布局、beforeLoad 重定向守卫、notFound()、head/meta;以及完整的路由器测试框架(内存历史记录工厂、基于文件的路由树变体、等待路由器就绪)。
  • references/query-integration.md — 了解 TanStack Query 的衔接点时加载:ensureQueryData + useSuspenseQuery 的协作、等待与延迟的取舍、queryOptions 工厂、变更后通过 router.invalidate() 进行失效处理、staleTime/gcTime/defaultPreloadStaleTime — 均从路由角度进行说明,并交叉引用规范的 tanstack-query 技能。
  • references/sources.md — 研究出处(官方 tanstack.com/router 文档页面 + 在 forge 验证的 npm 版本)。

此技能不包含 scripts/ 或 assets/。

正文预算

  • description ≤ 1,024 个字符(已遵守)。
  • 正文 ≤ 约 500 行/5,000 个 token — 每轮都会保留在上下文中;较长的内容存放在 references/ 中。
  • 每个参考文件:超过 10k 个 token 时警告,超过 25k 个 token 时错误。参考文件总量:超过 25k 个 token 时警告,超过 50k 个 token 时错误。

评论

支持 Markdown