DevToolBox免费
博客

TanStack Router 完全指南:React 类型安全路由 (2026)

18 分钟阅读作者 DevToolBox Team

TanStack Router 是一个完全类型安全的 React 路由库,为路由参数、搜索参数、加载器和导航提供端到端的类型安全。与依赖字符串路径和运行时验证的传统路由不同,TanStack Router 在编译时就能捕获路由错误。凭借内置的搜索参数管理、数据加载、代码分割和一流的开发工具,它是目前 React 应用最先进的路由解决方案。

TL;DR

TanStack Router 为 React 提供 100% 类型安全路由,编译时验证路由参数、搜索参数、加载器和链接。具备文件路由、Zod 搜索参数验证、自动代码分割、路由级数据加载、结构共享、开发工具和 SSR 支持(通过 TanStack Start)。以更优的开发体验和零运行时路由错误取代 React Router。

Key Takeaways
  • TanStack Router 提供端到端类型安全:路由参数、搜索参数、加载器、上下文和 Link 组件在编译时完全类型化。
  • 文件路由自动生成路由树,消除手动路由配置同时保留完整类型推断。
  • 内置 Zod 搜索参数管理取代了外部状态库进行基于 URL 的状态管理。
  • 路由加载器具备自动缓存、失效和去重功能,提供类似 Remix 或 Next.js 的数据获取层。
  • 文件路由自动代码分割,独立懒加载路由组件、加载器和搜索参数验证器。
  • TanStack Router 开发工具提供可视化路由检查、搜索参数调试和缓存监控。

什么是 TanStack Router?为什么要类型安全路由?

TanStack Router 是 Tanner Linsley(TanStack Query 的创建者)开发的 React 客户端路由库。它从一开始就将 TypeScript 作为一等公民,路由的每个方面都由编译器验证。路由路径、路径参数、搜索参数、加载器数据甚至 Link 的 href 属性都经过类型检查。

传统路由如 React Router 使用基于字符串的路径,不提供编译时保证。路由路径中的拼写错误或缺失的搜索参数只在运行时才会暴露。TanStack Router 通过将路由树编码到 TypeScript 类型系统中消除了这整类 bug。

# Install TanStack Router
npm install @tanstack/react-router

# Install the Vite plugin for file-based routing
npm install -D @tanstack/router-plugin @tanstack/router-devtools

# vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { TanStackRouterVite } from "@tanstack/router-plugin/vite";

export default defineConfig({
  plugins: [
    TanStackRouterVite(),
    react(),
  ],
});

TanStack Router vs React Router vs Next.js 路由对比

了解 TanStack Router 与其他路由方案的对比有助于决定何时采用它。以下是功能逐项对比:

功能TanStack RouterReact Router v7Next.js App Router
类型安全参数完整编译时类型部分(加载器类型)无(基于字符串)
类型安全搜索参数完整 Zod 验证手动解析手动解析
类型安全导航Link 属性完全类型化基于字符串的路径基于字符串的路径
数据加载带缓存的路由加载器加载器(Remix 风格)服务端组件 / fetch
代码分割自动按路由分割手动 lazy()自动按页面分割
搜索参数管理一等内置支持useSearchParams(基础)useSearchParams(基础)
SSR 支持通过 TanStack Start内置(Remix)内置
开发工具可视化路由开发工具无内置无内置

路由树和文件路由

TanStack Router 将路由组织为树形结构。你可以使用 createRoute 和 createRouter 手动定义路由,也可以使用文件路由从文件系统自动生成路由树。推荐使用文件路由,因为它消除了样板代码并保留完整的类型安全。

文件路由下,运行 TanStack Router CLI 或 Vite 插件会监听你的路由目录并生成 routeTree.gen.ts 文件,包含完整的类型安全路由树。

// File-based routing directory structure
// src/routes/
//   __root.tsx          -> Root layout
//   index.tsx           -> / (home page)
//   about.tsx           -> /about
//   posts.tsx           -> /posts (layout)
//   posts.index.tsx     -> /posts (index)
//   posts.$postId.tsx   -> /posts/:postId
//   _auth.tsx           -> Auth layout (pathless)
//   _auth.login.tsx     -> /login
//   _auth.register.tsx  -> /register

// src/routes/__root.tsx
import { createRootRoute, Outlet } from "@tanstack/react-router";

export const Route = createRootRoute({
  component: () => (
    <div>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/posts">Posts</Link>
        <Link to="/about">About</Link>
      </nav>
      <Outlet />
    </div>
  ),
});
// Manual route tree definition (alternative to file-based)
import {
  createRouter,
  createRootRoute,
  createRoute,
} from "@tanstack/react-router";

const rootRoute = createRootRoute({
  component: RootLayout,
});

const indexRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: "/",
  component: HomePage,
});

const postsRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: "posts",
  component: PostsLayout,
});

const postRoute = createRoute({
  getParentRoute: () => postsRoute,
  path: "\$postId",
  component: PostDetail,
});

const routeTree = rootRoute.addChildren([
  indexRoute,
  postsRoute.addChildren([postRoute]),
]);

const router = createRouter({ routeTree });

路由参数和搜索参数(类型安全)

路由参数(如 /posts/$postId 中的路径段)根据路由定义自动类型化。无需手动类型断言或运行时转换。在加载器或组件中访问 params 时,TypeScript 知道确切的形状。

// src/routes/posts.$postId.tsx
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/posts/\$postId")({
  loader: async ({ params }) => {
    // params.postId is typed as string automatically
    const post = await fetchPost(params.postId);
    return { post };
  },
  component: PostDetail,
});

function PostDetail() {
  const { post } = Route.useLoaderData();
  const { postId } = Route.useParams();
  // postId is typed as string, post is typed from loader
  return <h1>{post.title}</h1>;
}

类型安全搜索参数与验证

搜索参数是 TanStack Router 的杀手级功能。你不需要手动解析 URLSearchParams,只需定义验证模式(通常用 Zod),TanStack Router 自动处理序列化、反序列化和类型推断。搜索参数使用结构共享,只有变化的值才会触发重新渲染。

// src/routes/posts.index.tsx
import { createFileRoute } from "@tanstack/react-router";
import { z } from "zod";

const postsSearchSchema = z.object({
  page: z.number().default(1),
  sort: z.enum(["newest", "oldest", "popular"]).default("newest"),
  filter: z.string().optional(),
  tags: z.array(z.string()).default([]),
});

export const Route = createFileRoute("/posts/")({
  validateSearch: postsSearchSchema,
  component: PostsList,
});

function PostsList() {
  // All search params are fully typed!
  const { page, sort, filter, tags } = Route.useSearch();
  // page: number, sort: "newest" | "oldest" | "popular"
  // filter: string | undefined, tags: string[]

  return (
    <div>
      <Link
        to="/posts"
        search={{ page: page + 1, sort, filter, tags }}
      >
        Next Page
      </Link>
    </div>
  );
}

加载器和数据加载

TanStack Router 有内置数据加载系统,类似 Remix 加载器。每个路由可以定义一个在组件渲染前运行的加载器函数,接收类型化的路由上下文、参数和搜索参数,开箱即用支持缓存、失效和去重。

加载器与 TanStack Query 无缝集成。你可以在加载器中使用 ensureQueryData 预获取数据,组件通过 useQuery 消费,兼得路由级预获取和组件级缓存管理。

// Route loader with TanStack Query integration
import { createFileRoute } from "@tanstack/react-router";
import { queryOptions, useSuspenseQuery } from "@tanstack/react-query";

const postQueryOptions = (postId: string) =>
  queryOptions({
    queryKey: ["post", postId],
    queryFn: () => fetchPost(postId),
  });

export const Route = createFileRoute("/posts/\$postId")({
  loader: async ({ params, context }) => {
    // Prefetch the data into TanStack Query cache
    await context.queryClient.ensureQueryData(
      postQueryOptions(params.postId)
    );
  },
  component: PostDetail,
});

function PostDetail() {
  const { postId } = Route.useParams();
  // Data is already cached from the loader
  const { data: post } = useSuspenseQuery(
    postQueryOptions(postId)
  );
  return <h1>{post.title}</h1>;
}

路由上下文

路由上下文允许你在路由树中传递类型化数据。与 React context 不同,路由上下文按路由定义,从父路由流向子路由。适合传递认证状态、功能标志或共享服务。

在路由器级别用 createRouter 定义上下文,在每个路由级别用 beforeLoad 扩展。子路由接收所有父路由累积的上下文,完全类型化。

// Define router context with auth and queryClient
import { createRouter } from "@tanstack/react-router";
import { QueryClient } from "@tanstack/react-query";

interface RouterContext {
  queryClient: QueryClient;
  auth: { user: User | null; isAuthenticated: boolean };
}

const router = createRouter({
  routeTree,
  context: {
    queryClient: new QueryClient(),
    auth: undefined!,  // Will be provided in <RouterProvider>
  },
});

// In your app entry point
function App() {
  const auth = useAuth();
  return (
    <RouterProvider
      router={router}
      context={{ auth }}
    />
  );
}

布局路由和 Outlet

布局路由用共享 UI(如导航栏、侧边栏或页脚)包装子路由。TanStack Router 中,布局路由渲染其组件并使用 Outlet 显示当前匹配的子路由。文件路由使用下划线前缀目录或无路径路由文件定义布局。

TanStack Router 中的 Outlet 组件与 React Router 类似,但对渲染的子路由具有完整的类型安全。

// src/routes/posts.tsx - Layout route for /posts/*
import { createFileRoute, Outlet, Link } from "@tanstack/react-router";

export const Route = createFileRoute("/posts")({
  component: PostsLayout,
});

function PostsLayout() {
  return (
    <div style={{ display: "flex" }}>
      <aside>
        <h2>Posts</h2>
        <nav>
          <Link to="/posts" search={{ sort: "newest" }}>
            All Posts
          </Link>
          <Link to="/posts" search={{ sort: "popular" }}>
            Popular
          </Link>
        </nav>
      </aside>
      <main>
        {/* Child routes render here */}
        <Outlet />
      </main>
    </div>
  );
}

// src/routes/_auth.tsx - Pathless layout (no URL segment)
import { createFileRoute, Outlet } from "@tanstack/react-router";

export const Route = createFileRoute("/_auth")({
  component: () => (
    <div style={{ maxWidth: 400, margin: "0 auto" }}>
      <h1>Welcome</h1>
      <Outlet />
    </div>
  ),
});

导航:Link 和 useNavigate

TanStack Router 提供类型安全的 Link 组件和 useNavigate hook。Link 组件在编译时验证 to 属性、params 和 search params。如果链接到需要 postId 参数的路由但忘记提供,TypeScript 立即捕获错误。

useNavigate hook 提供具有相同类型安全保证的编程导航。可以导航到路由、更新搜索参数或替换历史记录条目,全部具有完整的自动补全和验证。

// Type-safe Link component
import { Link, useNavigate } from "@tanstack/react-router";

function Navigation() {
  const navigate = useNavigate();

  return (
    <div>
      {/* TypeScript validates all props */}
      <Link to="/posts/\$postId" params={{ postId: "123" }}>
        View Post
      </Link>

      {/* Search params are type-checked too */}
      <Link
        to="/posts"
        search={{ page: 1, sort: "newest" }}
        activeProps={{ style: { fontWeight: "bold" } }}
      >
        Posts
      </Link>

      {/* Programmatic navigation */}
      <button
        onClick={() =>
          navigate({
            to: "/posts/\$postId",
            params: { postId: "456" },
            search: { tab: "comments" },
          })
        }
      >
        Go to Post
      </button>

      {/* Update only search params (keep current route) */}
      <button
        onClick={() =>
          navigate({
            search: (prev) => ({ ...prev, page: prev.page + 1 }),
          })
        }
      >
        Next Page
      </button>
    </div>
  );
}

路由守卫和身份验证

TanStack Router 通过 beforeLoad hook 处理身份验证和路由保护。此函数在路由加载器和组件之前运行,是检查认证状态、重定向未认证用户或验证权限的理想位置。

因为 beforeLoad 可以访问完整的路由上下文,你可以通过路由上下文传递认证服务,在任何路由守卫中使用,无需 prop drilling 或全局状态。

// src/routes/_authenticated.tsx
import { createFileRoute, redirect } from "@tanstack/react-router";

export const Route = createFileRoute("/_authenticated")({
  beforeLoad: async ({ context, location }) => {
    if (!context.auth.isAuthenticated) {
      throw redirect({
        to: "/login",
        search: { redirect: location.href },
      });
    }
  },
  component: () => <Outlet />,
});

// src/routes/_authenticated.dashboard.tsx
// This route is automatically protected by the parent guard
export const Route = createFileRoute("/_authenticated/dashboard")({
  component: Dashboard,
});

function Dashboard() {
  // context.auth.user is guaranteed to exist here
  return <h1>Dashboard</h1>;
}

代码分割

TanStack Router 通过文件路由支持自动代码分割。在路由定义旁创建 .lazy.tsx 文件,路由器自动懒加载组件,将关键路由定义(参数、搜索参数、加载器)保留在主包中,同时延迟组件代码。

这种方式不仅分割组件,还独立分割加载器和搜索参数验证器,让你精细控制包大小。

// src/routes/posts.$postId.tsx - Critical route config (main bundle)
import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/posts/\$postId")({
  // Loader stays in main bundle for prefetching
  loader: async ({ params }) => fetchPost(params.postId),
});

// src/routes/posts.$postId.lazy.tsx - Lazy component (separate chunk)
import { createLazyFileRoute } from "@tanstack/react-router";

export const Route = createLazyFileRoute("/posts/\$postId")({
  component: PostDetail,
  pendingComponent: () => <div>Loading post...</div>,
  errorComponent: ({ error }) => (
    <div>Error: {error.message}</div>
  ),
});

function PostDetail() {
  const post = Route.useLoaderData();
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  );
}

TanStack Router 开发工具

TanStack Router 包含可视化开发工具面板,显示当前路由树、活跃路由匹配、搜索参数状态、加载器缓存和待处理导航。开发工具对于调试复杂路由场景和理解数据流非常有价值。

开发工具支持 tree-shaking,仅在开发构建中包含。添加一个组件即可在生产环境自动消失。

// Add devtools to your app
import { TanStackRouterDevtools } from "@tanstack/router-devtools";

// In your root route component
export const Route = createRootRoute({
  component: () => (
    <>
      <Outlet />
      {/* Only rendered in development */}
      <TanStackRouterDevtools position="bottom-right" />
    </>
  ),
});

SSR 集成与 TanStack Start

TanStack Start 是基于 TanStack Router 构建的全栈框架,添加了服务端渲染、服务器函数和部署适配器。它在服务端提供相同的类型安全路由,支持服务端数据加载、流式 SSR 和 API 路由。

如果需要 SSR,TanStack Start 是官方解决方案。对于客户端单页应用,TanStack Router 可与 Vite 和任何打包器独立使用。

// TanStack Start - app.config.ts
import { defineConfig } from "@tanstack/react-start/config";
import tsConfigPaths from "vite-tsconfig-paths";

export default defineConfig({
  vite: {
    plugins: [tsConfigPaths()],
  },
  server: {
    preset: "node-server",  // or vercel, netlify, cloudflare
  },
});

// Server function example
import { createServerFn } from "@tanstack/react-start";

const getServerTime = createServerFn()
  .validator(z.object({ timezone: z.string() }))
  .handler(async ({ data }) => {
    return new Date().toLocaleString("en-US", {
      timeZone: data.timezone,
    });
  });

从 React Router 迁移

从 React Router 迁移到 TanStack Router 可以增量进行。关键变化包括将 Route 组件替换为 createRoute 调用(或文件路由文件)、将 useParams/useSearchParams 转换为类型化等价物、以及替换 Link 组件。路由结构概念(嵌套路由、outlet、布局)可直接映射。

先将 TanStack Router 与 React Router 并行安装,每次迁移一个路由分支。Vite 插件和路由树生成器自动处理大部分配置。

// BEFORE: React Router v6
import { BrowserRouter, Routes, Route, useParams } from "react-router-dom";

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/posts" element={<PostsLayout />}>
          <Route index element={<PostsList />} />
          <Route path=":postId" element={<PostDetail />} />
        </Route>
      </Routes>
    </BrowserRouter>
  );
}

function PostDetail() {
  const { postId } = useParams(); // postId: string | undefined
  // No type safety!
}

// AFTER: TanStack Router (file-based)
// src/routes/__root.tsx
// src/routes/index.tsx
// src/routes/posts.tsx
// src/routes/posts.index.tsx
// src/routes/posts.$postId.tsx

// Each file exports a typed Route - params are guaranteed

Zod 搜索参数验证

Zod 是 TanStack Router 搜索参数的推荐验证库。在路由定义中定义 Zod 模式,TanStack Router 自动验证、序列化和类型化搜索参数。无效搜索参数会被捕获并可用默认值回退。

搜索参数验证在每次导航时运行,确保组件始终收到有效数据。结合结构共享,只有实际变化的搜索参数才触发组件重新渲染。

import { createFileRoute } from "@tanstack/react-router";
import { z } from "zod";

const productSearchSchema = z.object({
  category: z.enum(["electronics", "clothing", "books"]).catch("electronics"),
  priceMin: z.number().min(0).catch(0),
  priceMax: z.number().max(10000).catch(10000),
  inStock: z.boolean().catch(true),
  q: z.string().optional(),
});

type ProductSearch = z.infer<typeof productSearchSchema>;

export const Route = createFileRoute("/products")({
  validateSearch: productSearchSchema,
  component: ProductsPage,
});

function ProductsPage() {
  const search = Route.useSearch();
  // search is fully typed as ProductSearch
  // Invalid URL params are caught and replaced with defaults

  return (
    <div>
      <Link
        to="/products"
        search={(prev) => ({ ...prev, category: "books" })}
      >
        Books
      </Link>
    </div>
  );
}

待处理和错误状态

TanStack Router 内置支持路由级别的待处理 UI(导航时的加载状态)和错误边界。每个路由可以定义在数据加载时显示的 pendingComponent 和加载器失败时渲染的 errorComponent。

pendingMs 和 pendingMinMs 选项控制待处理 UI 何时出现以及显示多长时间,防止快速导航时的闪烁加载状态。

// src/routes/posts.$postId.tsx
export const Route = createFileRoute("/posts/\$postId")({
  loader: async ({ params }) => {
    const post = await fetchPost(params.postId);
    if (!post) throw new Error("Post not found");
    return { post };
  },
  // Show after 1s of loading (avoid flash for fast loads)
  pendingMs: 1000,
  // Show for at least 500ms once visible
  pendingMinMs: 500,
  pendingComponent: () => (
    <div style={{ padding: "2rem", textAlign: "center" }}>
      <span>Loading post...</span>
    </div>
  ),
  errorComponent: ({ error, reset }) => (
    <div style={{ padding: "2rem", color: "red" }}>
      <h2>Something went wrong</h2>
      <p>{error.message}</p>
      <button onClick={reset}>Try again</button>
    </div>
  ),
  component: PostDetail,
});

最佳实践

遵循这些最佳实践,在生产应用中充分发挥 TanStack Router 的优势。

  • 使用文件路由实现自动代码分割、路由树生成和一致的项目结构。
  • 为每个使用 URL 状态的路由用 Zod 定义搜索参数模式,确保类型安全和自动验证。
  • 使用路由上下文传递认证状态和共享服务,而非使用全局 React context。
  • 利用搜索参数的结构共享防止部分参数变化时不必要的重新渲染。
  • 将路由组件分割到 .lazy.tsx 文件,保持主包小巧并延迟重型组件代码。
  • 使用 ensureQueryData 集成 TanStack Query 与路由加载器,实现乐观预获取和组件级缓存控制。
  • 使用 beforeLoad 进行身份验证守卫,而非包装组件,因为它在任何渲染或数据加载之前运行。
  • 配置 pendingMs(默认 1000ms)和 pendingMinMs,避免快速导航时的闪烁加载同时为慢导航显示反馈。
  • 开发时保持 TanStack Router 开发工具启用,可视化调试路由匹配、参数和缓存状态。
  • 使用 router.invalidate() 在变更后重新获取加载器数据,而非手动管理缓存失效。

常见问题

什么是 TanStack Router?

TanStack Router 是一个完全类型安全的 React 客户端路由库。提供路由路径、参数、搜索参数、加载器和导航链接的编译时验证。由 Tanner Linsley 创建,是 TanStack 生态的一部分。

TanStack Router 与 React Router 有什么不同?

TanStack Router 提供 React Router 缺乏的端到端类型安全。路由参数、搜索参数和 Link 组件在编译时完全类型化,还包含内置 Zod 搜索参数管理、自动代码分割、路由级数据缓存和可视化开发工具。

使用 TanStack Router 需要 TanStack Start 吗?

不需要。TanStack Router 可作为独立客户端路由库与 Vite 或任何打包器配合使用。TanStack Start 仅在需要服务端渲染、服务器函数或全栈框架时才需要。

TanStack Router 的搜索参数如何工作?

搜索参数通过路由定义中的验证模式(通常是 Zod)定义。路由器自动序列化对象为 URL 搜索字符串并反序列化,具有完整类型安全。结构共享确保组件仅在使用的特定搜索参数实际变化时重新渲染。

可以从 React Router 增量迁移到 TanStack Router 吗?

可以。可以将 TanStack Router 与 React Router 并行安装,每次迁移一个路由分支。路由概念直接映射,从叶子路由开始向上迁移。

TanStack Router 支持代码分割吗?

支持。文件路由下代码分割是自动的。在路由定义旁创建 .lazy.tsx 文件,路由器自动懒加载组件,无需手动 lazy() 调用。

TanStack Router 的数据加载如何工作?

每个路由可定义在组件渲染前运行的加载器函数,接收类型化的参数、搜索参数和路由上下文,支持缓存、自动失效和去重。可集成 TanStack Query 使用 ensureQueryData 实现高级缓存。

TanStack Router 可以用于生产环境吗?

可以。TanStack Router 已达到稳定 v1 状态,被各种规模的公司用于生产。具有完善的文档、活跃的维护和不断增长的生态系统。

𝕏 Twitterin LinkedIn
这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

试试这些相关工具

{ }JSON FormatterTSJSON to TypeScript%20URL Encoder/Decoder

相关文章

React 设计模式指南:复合组件、自定义 Hook、HOC、Render Props 与状态机

完整的 React 设计模式指南,涵盖复合组件、render props、自定义 hooks、高阶组件、Provider 模式、状态机、受控与非受控、组合模式、观察者模式、错误边界和模块模式。

React Query 模式 2026:TanStack Query 数据获取、缓存与变更

2026年精通React Query (TanStack Query) 模式:useQuery、useMutation、乐观更新与服务器状态管理。

Remix 完全指南:基于 Web 标准的全栈框架 (2026)

完整的 Remix 指南,涵盖 Loaders、Actions、嵌套路由、错误边界、流式渲染、资源路由、认证、部署和从 Next.js 迁移。