Doramagic 项目包 · 项目说明书

router 项目

生成时间:2026-06-01 00:26:58 UTC

入门指南

TanStack Router 是一个现代化路由库,专为类型安全、数据驱动导航和卓越的开发体验而设计。本指南将帮助你快速上手 TanStack Router,掌握其核心概念和基本用法。

章节 相关页面

继续阅读本节完整说明和来源证据。

章节 前提条件

继续阅读本节完整说明和来源证据。

章节 安装步骤

继续阅读本节完整说明和来源证据。

章节 开发依赖

继续阅读本节完整说明和来源证据。

什么是 TanStack Router

TanStack Router 是 TanStack 生态系统中的核心路由解决方案,提供以下核心特性:

  • 端到端类型安全:路由、参数、加载器均具有完整的类型推导
  • Schema 驱动的搜索参数:支持验证的搜索参数处理
  • 内置缓存、预取与失效机制:开箱即用的数据管理
  • 嵌套布局、过渡动画和错误边界:灵活的页面组织方式

资料来源:README.md:1-20

安装与环境配置

前提条件

  • Node.js >= 18.0.0
  • pnpm、npm 或 bun 作为包管理器

安装步骤

根据你的框架选择对应的包进行安装:

框架安装命令
Reactpnpm add @tanstack/react-router
Solidpnpm add @tanstack/solid-router

资料来源:examples/react/quickstart/package.json:1-30

开发依赖

确保安装必要的开发依赖:

pnpm add -D vite

创建第一个路由

基本项目结构

my-app/
├── src/
│   ├── routeTree.gen.ts      # 自动生成的路由类型
│   ├── routes/
│   │   ├── index.tsx         # 首页路由
│   │   └── about.tsx         # 关于页路由
│   ├── main.tsx              # 应用入口
│   └── router.tsx            # 路由配置
├── package.json
└── vite.config.ts

定义路由

使用 TanStack Router 的类型安全路由定义方式:

// routes/home.tsx
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/')({
  component: HomeComponent,
})

function HomeComponent() {
  return (
    <div>
      <h1>欢迎使用 TanStack Router</h1>
    </div>
  )
}

资料来源:packages/router-core/src/index.ts:1-50

配置路由

创建路由配置文件:

import { createRouter, createRootRoute } from '@tanstack/react-router'

const rootRoute = createRootRoute({
  component: () => (
    <>
      <Outlet />
      <div>Footer</div>
    </>
  ),
})

const indexRoute = createFileRoute('/')()

const aboutRoute = createFileRoute('/about')()

const routeTree = rootRoute.addChildren([
  indexRoute,
  aboutRoute,
])

export const router = createRouter({ routeTree })

资料来源:packages/router-core/src/router.ts:1-40

核心概念

路由层级与嵌套

TanStack Router 支持嵌套路由布局,允许你构建复杂的页面层级结构:

graph TD
    A[Root Route] --> B[Dashboard Layout]
    A --> C[Settings Layout]
    B --> D[Dashboard Index]
    B --> E[Dashboard Analytics]
    C --> F[Settings Profile]
    C --> G[Settings Security]

嵌套路由通过 addChildren 方法组合:

const dashboardRoute = createFileRoute('/dashboard')({
  component: DashboardLayout,
})

const dashboardIndexRoute = createFileRoute('/dashboard/')()
const analyticsRoute = createFileRoute('/dashboard/analytics')()

const routeTree = rootRoute.addChildren([
  dashboardRoute.addChildren([
    dashboardIndexRoute,
    analyticsRoute,
  ]),
])

路由参数

TanStack Router 提供完整的类型安全参数支持:

// 定义带参数的路由
const userRoute = createFileRoute('/users/$userId')({
  component: UserProfile,
})

// 在组件中访问参数
function UserProfile() {
  const { userId } = Route.useParams()
  return <div>用户ID: {userId}</div>
}

数据加载器

使用 beforeLoadloader 进行数据预取:

const usersRoute = createFileRoute('/users')({
  beforeLoad: async () => {
    const users = await fetchUsers()
    return { users }
  },
  component: UsersComponent,
})

function UsersComponent() {
  const { users } = Route.useLoaderData()
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}

资料来源:packages/router-core/src/router.ts:80-120

开发与构建

开发模式

启动开发服务器:

pnpm dev

开发模式支持热模块替换(HMR),文件修改后自动重新构建资源。

资料来源:examples/react/start-basic/README.md:20-25

生产构建

构建应用用于生产环境:

pnpm build

构建产物将输出到 dist 目录,可部署到任何静态托管服务。

资料来源:examples/react/start-basic/README.md:30-35

TanStack Start 全栈框架

如果你需要服务端渲染(SSR)、流式传输和服务器函数等全栈功能,可以选择 TanStack Start:

pnpm add @tanstack/react-start

TanStack Start 在 TanStack Router 基础上增加了:

  • 全文档 SSR 和流式传输
  • 服务器函数与端到端类型安全
  • 部署就绪的打包和构建

资料来源:README.md:45-55

快速启动 TanStack Start 项目

# 基于示例创建新项目
npx gitpick TanStack/router/tree/main/examples/react/start-basic start-basic

# 进入目录并安装依赖
cd start-basic
pnpm install

# 启动开发服务器
pnpm dev

资料来源:examples/react/start-rscs/README.md:15-25

常见问题与注意事项

认证性能问题

社区反馈表明,使用推荐的认证模式时性能可能受到影响。onBeforeLoad 在每次页面导航时都会执行,即使在已认证状态下也是如此。优化建议:

  • 合理设计路由层级,避免在根路由或高频访问路由上执行重量级检查
  • 考虑使用缓存策略减少重复的认证请求

资料来源:社区问题 #3997

样式加载问题

在使用 TanStack Start 时,CSS Modules 样式可能仅在客户端加载,导致初始渲染时出现样式闪烁(FOUC)。确保在服务端正确配置样式加载机制。

资料来源:社区问题 #3023

无障碍访问

当前版本在路由导航时缺少路由公告和焦点管理。如需完整的无障碍支持,可能需要额外实现路由转换时的 ARIA 公告和焦点控制逻辑。

资料来源:社区问题 #918

扩展资源

下一步

资料来源:README.md:1-20

基于文件的路由系统

TanStack Router 的基于文件的路由系统(File-Based Routing)是一种通过文件系统结构自动生成路由配置的开发范式。开发者只需按照约定的目录和文件命名规范组织代码,系统即可自动解析路由层级、路径参数、布局结构,并生成类型安全的路由树。

章节 相关页面

继续阅读本节完整说明和来源证据。

章节 路由生成器架构

继续阅读本节完整说明和来源证据。

章节 路由节点解析

继续阅读本节完整说明和来源证据。

章节 基础命名规则

继续阅读本节完整说明和来源证据。

概述

TanStack Router 的基于文件的路由系统(File-Based Routing)是一种通过文件系统结构自动生成路由配置的开发范式。开发者只需按照约定的目录和文件命名规范组织代码,系统即可自动解析路由层级、路径参数、布局结构,并生成类型安全的路由树。

该系统的核心由 @tanstack/router-generator 包提供支持,负责扫描指定目录结构、解析文件命名约定,并生成 routeTree.gen.ts 路由树文件。

资料来源:packages/router-generator/src/generator.ts:1-50

核心概念

路由生成器架构

路由生成器采用物理文件系统扫描和虚拟文件系统两种模式,支持灵活的项目结构配置。

graph TD
    A[配置 routeGenerator] --> B[路由生成器扫描]
    B --> C{扫描模式}
    C -->|物理文件系统| D[physical/getRouteNodes.ts]
    C -->|虚拟文件系统| E[virtual/getRouteNodes.ts]
    D --> F[解析路由节点]
    E --> F
    F --> G[生成 routeTree.gen.ts]
    G --> H[类型安全的路由 API]

资料来源:packages/router-generator/src/generator.ts:100-200

路由节点解析

路由节点是文件系统与路由配置的桥梁,每个文件或目录都会被解析为对应的路由节点。

节点类型文件命名示例生成的路由配置
静态路由about.tsx/about
动态路由$postId.tsx/[postId]
布局组件_layout.tsx不增加路径层级
索引路由index.tsx/
通配符路由$.tsx/*
嵌套布局posts/_layout.tsx/posts 布局

资料来源:packages/router-generator/src/filesystem/physical/getRouteNodes.ts:1-80

文件命名约定

TanStack Router 定义了一套完整的文件命名约定,用于表达路由的各种特性。

基础命名规则

文件名模式说明生成路径
home.tsx静态路由/home
about.tsx静态路由/about
posts.tsx静态路由/posts
posts-list.tsx静态路由/posts-list

资料来源:docs/router/routing/file-naming-conventions.md:1-50

动态路由参数

动态段使用 $ 前缀标识,生成的路径参数可自动推断类型。

文件名模式说明生成路径
$postId.tsx必需参数/[postId]
$$postId.tsx可选参数/[postId]?
$postId.$commentId.tsx多参数/[postId]/[commentId]
// 生成的路由参数类型
interface PostIdPostCommentIdRouteParams {
  postId: string
  commentId: string
}

资料来源:packages/router-generator/src/filesystem/physical/getRouteNodes.ts:50-100

布局与特殊文件

下划线前缀的文件用于特殊功能,不会增加 URL 路径层级。

文件名模式说明行为
_layout.tsx布局组件作为子路由的共享布局
_index.tsx索引布局布局的默认内容
_root.tsx根布局应用顶层布局
_.tsx通配符布局捕获所有未匹配路由
// 文件结构示例
// src/routes/
//   _root.tsx          // 应用根布局
//   _layout.tsx        // 未使用(_root 已是根)
//   posts/
//     _layout.tsx      // /posts 及其子路由的布局
//     index.tsx        // /posts
//     $postId.tsx      // /posts/:postId
// ```

资料来源:[docs/router/routing/file-naming-conventions.md:60-120]()

## 路由生成流程

### 生成器配置

在 `vite.config.ts` 或 `package.json` 中配置路由生成器:

import { defineConfig } from 'vite' import { router } from '@tanstack/react-start/config'

export default defineConfig({ plugins: [ router({ routeFileIgnorePattern: '/^_private/', }), ], })


| 配置项 | 类型 | 说明 |
|-------|------|------|
| `routeFileIgnorePattern` | `RegExp` | 忽略匹配的文件 |
| `generatedRouteTree` | `string` | 生成的路由树文件路径 |

资料来源:[packages/router-generator/src/generator.ts:200-300]()

### 扫描与解析流程

sequenceDiagram participant DevServer as 开发服务器 participant Generator as 路由生成器 participant FileSystem as 文件系统 participant RouteTree as routeTree.gen.ts

DevServer->>Generator: 启动时扫描 Generator->>FileSystem: 读取 routes/ 目录 FileSystem-->>Generator: 文件列表 Generator->>Generator: 解析命名约定 Generator->>Generator: 构建路由节点树 Generator->>RouteTree: 生成类型安全代码 RouteTree-->>DevServer: 可用路由类型 DevServer->>DevServer: 启动热更新监听 FileSystem->>DevServer: 文件变更通知 DevServer->>Generator: 重新生成


资料来源:[packages/router-generator/src/generator.ts:150-250]()

## Monorepo 支持

TanStack Router 的文件路由系统原生支持 Monorepo 结构,可以将路由定义、组件库和业务逻辑分离在不同的包中。

### 推荐的 Monorepo 结构

my-app/ ├── packages/ │ ├── router/ # 路由定义和类型增强 │ │ ├── src/ │ │ │ ├── routeTree.gen.ts │ │ │ ├── routes/ │ │ │ │ ├── _root.tsx │ │ │ │ └── posts/ │ │ │ │ ├── _layout.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── $postId.tsx │ │ │ └── index.ts │ └── features/ # 功能模块包 │ └── posts/ │ └── src/ │ └── components/ └── apps/ └── web/ # 主应用 └── src/ └── main.tsx


资料来源:[examples/react/router-monorepo-simple-lazy/README.md:1-50]()

### 延迟加载路由

在 Monorepo 中,推荐使用延迟加载(lazy loading)来实现代码分割:

// packages/features/posts/src/index.ts import { createLazyRoute } from '@tanstack/react-router'

export const postsRoute = createLazyRoute('/posts')({ component: () => import('./components/PostsList').then(m => m.PostsList), })

// packages/router/src/routes/_posts.tsx import { Route } from '@tanstack/react-router' import { postsRoute } from '@features/posts'

export const postsRouteRoute = new Route({ getParentRoute: () => rootRoute, ...postsRoute, })


资料来源:[examples/react/router-monorepo-simple-lazy/README.md:80-120]()

## 类型安全特性

### 自动生成的类型定义

路由生成器会自动创建完整的类型定义,确保路由参数、搜索参数和布局上下文都是类型安全的。

// routeTree.gen.ts 自动生成 export interface RouteTree { routeTree: { fullPath: '/' staticData: Record<string, never> userLoaderContext: object validateSearch: Record<string, never> parseParams: Record<string, never> stringifyParams: Record<string, never> routeContext: Record<string, never> useParams: Record<string, never> routes: { __root: typeof __rootRoute posts: typeof postsRoute postsId: typeof postsIdRoute } } }


资料来源:[packages/router-core/src/router.ts:1-100]()

### 搜索参数类型推断

通过 Zod schema 定义搜索参数,可获得自动类型推断和运行时验证:

import { z } from 'zod'

export const searchParams = { schema: { page: z.number().optional(), limit: z.number().optional(), } } as const

// 使用时获得完整类型 const { page, limit } = useSearch({ from: '/posts' }) // page: number | undefined // limit: number | undefined


资料来源:[packages/react-router/src/headContentUtils.tsx:1-50]()

## 高级配置

### 自定义路由树生成路径

import { router } from '@tanstack/react-start/config'

export default defineConfig({ plugins: [ router({ generatedRouteTree: 'src/generated/routeTree.gen.ts', }), ], })


### 文件排除模式

使用正则表达式排除不需要生成路由的文件:

router({ routeFileIgnorePattern: /^_|\.private\./, })


| 排除模式 | 匹配示例 | 说明 |
|---------|---------|------|
| `/^_/` | `_private.tsx` | 下划线开头的文件 |
| `/\.private\./` | `api.private.tsx` | 包含 .private 的文件 |
| `/node_modules/` | `node_modules/test.tsx` | 依赖包中的文件 |

资料来源:[packages/router-generator/src/generator.ts:300-400]()

## 常见问题与社区反馈

### 性能考量

社区反馈 #3997 指出,在使用推荐的身份验证模式时,`_root` 或 `_authed` 路由的 `beforeLoad` 会在每次页面导航时执行,可能影响性能。建议优化方案包括:

- 使用 `cache` 选项缓存加载结果
- 避免在根路由布局中执行重型操作
- 利用 React 的 `useMemo` 减少不必要的重新渲染

### CSS Modules 支持

社区反馈 #3023 报告了 CSS Modules 样式在 SSR 场景下的样式闪烁(FOUC)问题。这是因为样式只在客户端加载导致初始渲染时无样式。建议使用 TanStack Start 的内置样式处理机制。

### 可访问性问题

社区反馈 #918 提到 TanStack Router v1 尚未实现承诺的可访问性功能,包括路由公告和焦点管理。建议关注官方更新或等待未来版本支持。

资料来源:[GitHub Issues #3997](https://github.com/TanStack/router/issues/3997), [#3023](https://github.com/TanStack/router/issues/3023), [#918](https://github.com/TanStack/router/issues/918)

## 总结

TanStack Router 的基于文件的路由系统通过声明式的文件命名约定和自动化代码生成,大幅简化了路由配置工作。核心优势包括:

| 特性 | 描述 |
|-----|------|
| 类型安全 | 自动生成完整的 TypeScript 类型定义 |
| 代码分割 | 零配置延迟加载支持 |
| 布局系统 | 灵活的多层布局嵌套 |
| 搜索参数 | 内置 Zod schema 验证 |
| 热更新 | 开发环境实时重新生成 |

开发者只需遵循命名约定组织文件结构,即可获得生产级的路由管理能力。

资料来源:packages/router-generator/src/generator.ts:1-50

快速启动模板

快速启动模板是 TanStack Router 为开发者提供的开箱即用项目模板,旨在帮助开发者快速搭建基于 TanStack Router 的应用。这些模板覆盖了主流的构建工具,包括 Vite、Webpack 和 Rspack,支持文件路由和代码分割等核心功能。模板设计遵循最佳实践,提供了完整的项目结构、路由配置示例和开发服务器集成,使开发者能够在几分钟内启动一个新项目。

章节 相关页面

继续阅读本节完整说明和来源证据。

章节 Vite 模板

继续阅读本节完整说明和来源证据。

章节 Webpack 模板

继续阅读本节完整说明和来源证据。

章节 Rspack 模板

继续阅读本节完整说明和来源证据。

概述

快速启动模板是 TanStack Router 为开发者提供的开箱即用项目模板,旨在帮助开发者快速搭建基于 TanStack Router 的应用。这些模板覆盖了主流的构建工具,包括 Vite、Webpack 和 Rspack,支持文件路由和代码分割等核心功能。模板设计遵循最佳实践,提供了完整的项目结构、路由配置示例和开发服务器集成,使开发者能够在几分钟内启动一个新项目。

TanStack Router 是一个类型安全的现代路由库,专为 React 和 Solid 等框架设计。它提供了声明式的路由定义、嵌套路由、路由匹配、加载状态管理和搜索参数处理等功能。通过快速启动模板,开发者可以避免繁琐的初始配置,直接专注于业务逻辑开发。

模板类型

TanStack Router 提供了三种官方快速启动模板,分别针对不同的构建工具进行优化。每种模板都包含相同的核心功能,只是构建工具的配置方式有所不同。

模板名称构建工具特点适用场景
quickstart-vite-file-basedVite原生 ESM 支持,快速热更新首选推荐,现代前端开发
quickstart-webpack-file-basedWebpack成熟稳定,广泛兼容遗留项目,企业环境
quickstart-rspack-file-basedRspackRust 实现,更快的构建速度大型项目,性能敏感场景

Vite 模板

Vite 模板是官方推荐的首选模板,充分利用 Vite 的原生 ESM 架构和极快的开发服务器启动速度。该模板配置了完整的文件路由系统和路由自动生成功能,开发者只需在特定目录下创建文件即可自动生成对应的路由。

npx gitpick TanStack/router/tree/main/examples/react/quickstart-vite-file-based quickstart-vite-file-based

Vite 模板的核心优势在于其极快的热更新速度。借助 Vite 的按需编译机制,即使项目规模较大,开发体验依然流畅。此外,Vite 模板支持零配置的 SSR 和客户端渲染切换,开发者可以根据需求灵活选择渲染模式。

Webpack 模板

Webpack 模板为需要在 Webpack 环境中使用 TanStack Router 的项目提供支持。虽然 Webpack 的构建速度相对较慢,但其成熟的生态系统和广泛的第三方插件支持使其在某些场景下仍然是首选。

npx gitpick TanStack/router/tree/main/examples/react/quickstart-webpack-file-based quickstart-webpack-file-based

Webpack 模板特别适合已经使用 Webpack 构建的项目进行渐进式迁移,或者需要使用特定 Webpack 插件的企业级应用。模板中包含了与 Webpack 5 的完整集成配置。

Rspack 模板

Rspack 模板是一个较新的选择,Rspack 是由 ByteDance 开发的构建工具,兼容 Webpack 的 API 但使用 Rust 实现,能够提供显著更快的构建速度。

npx gitpick TanStack/router/tree/main/examples/react/quickstart-rspack-file-based quickstart-rspack-file-based

对于构建时间敏感的大型项目,Rspack 模板是一个值得考虑的选择。它提供了与 Webpack 模板相同的功能,同时拥有接近 Vite 的构建性能。

项目结构

标准目录布局

快速启动模板采用一致的项目结构,便于开发者理解和维护。以下是典型的目录结构:

├── src/
│   ├── routes/
│   │   ├── __root.tsx          # 根路由组件
│   │   ├── index.tsx           # 首页路由
│   │   ├── about.tsx           # 关于页面
│   │   └── posts/
│   │       ├── index.tsx       # 文章列表页
│   │       └── $postId.tsx     # 文章详情页
│   ├── main.tsx                # 应用入口
│   ├── App.tsx                 # 根组件
│   └── router.ts               # 路由配置
├── package.json
├── vite.config.ts              # Vite 配置
└── tsconfig.json

路由文件约定

TanStack Router 的文件路由系统遵循明确的文件命名约定,这些约定决定了路由的路径和参数。

文件命名模式路由路径说明
index.tsx/目录的索引页面
about.tsx/about静态路径页面
$postId.tsx/posts/:postId动态路径参数
posts_.tsx/posts路径层级调整
(auth).tsx-路由组,不影响 URL
_hidden.tsx-隐藏路由,不生成链接

路由文件必须从 @tanstack/react-router 导出路由定义。TanStack Router 的代码生成工具会根据这些文件自动生成类型安全的路由配置。

核心配置

路由实例创建

路由实例是 TanStack Router 的核心,通过 createRouter 函数创建。以下是标准配置流程:

import { createRouter, createRouteTree } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'

const router = createRouter({
  routeTree,
  defaultPreload: 'intent',
  context: {
    // 应用级上下文
  },
})

资料来源:packages/router-core/src/router.ts:1-30

路由预加载策略

快速启动模板默认配置了 defaultPreload: 'intent',这意味着只有在用户悬停或聚焦链接时才会预加载路由。这种策略可以在不浪费带宽的情况下提升导航速度。

预加载模式描述
none不预加载任何路由
intent用户意图(悬停/聚焦)时预加载
viewport路由进入视口时预加载
always始终预加载

资料来源:packages/router-core/src/index.ts:50-60

路由状态管理

TanStack Router 使用响应式存储系统管理路由状态。核心状态包括当前路径、匹配的路由、查询参数和加载状态。

graph TD
    A[用户导航] --> B[路由变更]
    B --> C[匹配路由]
    C --> D[加载数据]
    D --> E[更新UI]
    E --> F[完成导航]

开发工作流

启动开发服务器

所有快速启动模板使用相同的基础命令:

# 安装依赖
pnpm install

# 启动开发服务器
pnpm dev

开发服务器支持热模块替换(HMR),路由文件修改后会自动重新生成路由类型定义,无需手动干预。

资料来源:examples/react/start-basic/README.md:15-20

构建生产版本

pnpm build

构建过程会自动进行代码分割、Tree Shaking 和压缩优化。每个路由页面都会被分割成独立的 chunk,实现真正的按需加载。

类型生成

TanStack Router 使用代码生成工具自动生成路由类型定义:

pnpm route:generate    # 生成路由类型
pnpm route:watch      # 监听文件变化自动生成

生成的类型定义文件(如 routeTree.gen.ts)提供了完整的类型安全保证,包括路由参数、查询参数和布局 Props 的类型推导。

路由组件

根路由组件

__root.tsx 是应用的根路由组件,定义全局布局和导航结构:

import { createRootRoute, createOutlet, Link } from '@tanstack/react-router'

export const Route = createRootRoute({
  component: RootComponent,
})

function RootComponent() {
  return (
    <div>
      <nav>
        <Link to="/">首页</Link>
        <Link to="/about">关于</Link>
      </nav>
      <Outlet />
    </div>
  )
}

嵌套路由

嵌套路由允许创建复杂的布局层级:

graph TD
    A[__root.tsx] --> B[posts/index.tsx]
    A --> C[about.tsx]
    B --> D[posts/$postId.tsx]

子路由通过 <Outlet /> 组件在父路由组件中渲染。

常见问题与社区反馈

性能问题

根据社区反馈,在使用推荐的认证模式时可能出现性能问题。当 _root_authed 路由配置了 onBeforeLoad 时,每次页面导航都会触发该回调,即使用户已经完成认证。

Issue #3997 报告了这个问题,建议开发者考虑使用中间件或更细粒度的加载策略来优化性能。

资料来源:社区讨论 #3997

CSS 模块样式闪烁

社区 Issue #3023 报告了使用 CSS Modules 时的样式闪烁问题(FOUC)。样式只在客户端加载,导致首次渲染时出现无样式内容闪烁。

这个问题特别影响 TanStack Start 项目,建议的解决方案包括:

  • 使用服务端渲染(SSR)模式
  • 配置样式预加载
  • 使用 CSS-in-JS 方案替代

资料来源:社区讨论 #3023

辅助功能缺失

Issue #918 指出 TanStack Router 在辅助功能方面存在不足。虽然 v1.0 版本承诺了无障碍支持,但实际实现中缺少路由公告和焦点管理功能。

建议在路由配置中手动添加 ARIA 属性和焦点控制:

const Route = createFileRoute('/about')({
  component: AboutComponent,
  loader: () => {
    // 更新文档标题
    document.title = '关于页面'
    return {}
  }
})

资料来源:社区讨论 #918

进阶功能

延迟加载

快速启动模板支持路由级别的延迟加载,用于优化大型应用的初始加载时间:

const AboutComponent = lazy(() => import('./about'))

延迟加载会将每个路由打包成独立的 chunk,只在用户首次访问该路由时才会加载对应的代码。

数据加载

TanStack Router 提供了强大的数据加载能力,在路由配置中使用 beforeLoadloader 钩子:

const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params }) => {
    const post = await fetchPost(params.postId)
    return { post }
  },
})

搜索参数管理

模板内置了搜索参数的类型安全处理:

const Route = createFileRoute('/posts')({
  validateSearch: (search: Record<string, unknown>) => {
    return {
      page: Number(search.page ?? 1),
      limit: Number(search.limit ?? 10),
    }
  },
})

与 TanStack Start 的关系

快速启动模板专注于基于文件路由的标准 React 应用,而 TanStack Start 提供了更完整的服务端渲染、API 路由和服务器函数支持。对于需要 SSR、API 路由或服务端数据处理的应用,建议使用 TanStack Start 替代快速启动模板。

特性快速启动模板TanStack Start
文件路由
客户端渲染
服务端渲染
API 路由
服务器函数
RSC 支持

总结

快速启动模板为 TanStack Router 提供了便捷的入门方式,通过提供预配置的项目模板,开发者可以快速启动新项目而无需深入了解构建工具配置。三种模板分别针对不同的构建工具进行了优化,开发者可以根据项目需求和技术栈偏好进行选择。

模板遵循 TanStack Router 的最佳实践,包括文件路由约定、类型安全路由定义和代码分割策略。同时,开发者应关注社区中报告的性能和辅助功能问题,以便在项目中及时采取相应的优化措施。

资料来源:examples/react/kitchen-sink/README.md:1-30

资料来源:packages/router-core/src/router.ts:1-30

核心包架构

TanStack Router 是一个类型安全的路由库,采用模块化架构设计,核心功能与框架特定实现分离。本页面详细说明其核心包架构、各组件职责及相互关系。

章节 相关页面

继续阅读本节完整说明和来源证据。

章节 router-core

继续阅读本节完整说明和来源证据。

章节 路由生成器

继续阅读本节完整说明和来源证据。

章节 Vite 插件

继续阅读本节完整说明和来源证据。

架构概览

TanStack Router 的核心包架构由多个层次分明的模块组成,形成了一个清晰的分层架构:

graph TD
    subgraph "表现层"
        A[React Router]
        B[Solid Router]
        C[TanStack Start]
    end
    
    subgraph "核心层"
        D[router-core]
    end
    
    subgraph "工具层"
        E[router-generator]
        F[router-plugin]
        G[start-server-core]
    end
    
    subgraph "基础设施层"
        H[@tanstack/history]
        I[@tanstack/loading]
        J[@tanstack/router-core]
    end
    
    A --> D
    B --> D
    C --> D
    C --> G
    E --> F
    F --> D
    D --> H
    D --> I
    D --> J

核心包职责

router-core

router-core 是整个路由系统的核心引擎,负责路由匹配、状态管理、导航控制等基础功能。资料来源:packages/router-core/src/router.ts:1-50

模块职责主要导出
router.ts路由器主类,生命周期管理Router, RouterOptions
qss.ts查询字符串编解码encode, toValue
path.ts路径处理工具resolvePath, interpolatePath
searchParams.ts搜索参数解析与序列化defaultParseSearch, defaultStringifySearch
new-process-route-tree.ts路由树构建与匹配processRouteTree, buildRouteBranch

#### 查询字符串处理

qss.ts 模块重新实现了 qss 包的功能,使用现代浏览器 API(URLSearchParams)进行查询字符串编码:资料来源:packages/router-core/src/qss.ts:1-40

export function encode(
  obj: Record<string, any>,
  stringify: (value: any) => string = String,
): string {
  const result = new URLSearchParams()
  for (const key in obj) {
    const val = obj[key]
    if (val !== undefined) {
      result.set(key, stringify(val))
    }
  }
  return result.toString()
}

该实现针对单一值键场景进行了优化,确保编码行为符合 TanStack Router 的使用场景。

#### 路由器核心组件

Router 类是整个系统的核心,负责管理路由状态、导航和匹配逻辑:资料来源:packages/router-core/src/router.ts:1-30

import { createBrowserHistory, parseHref } from '@tanstack/history'
import {
  DEFAULT_PROTOCOL_ALLOWLIST,
  createControlledPromise,
  decodePath,
  deepEqual,
  encodePathLikeUrl,
  findLast,
  functionalUpdate,
  hasKeys,
  isDangerousProtocol,
  last,
  nullReplaceEqualDeep,
  replaceEqualDeep,
} from './utils'
import {
  buildRouteBranch,
  findFlatMatch,
  findRouteMatch,
  findSingleMatch,
  processRouteMasks,
  processRouteTree,
} from './new-process-route-tree'

路由生成器

router-generator 负责在构建时生成类型安全的路由代码,实现端到端的类型推断:资料来源:packages/router-generator/src/index.ts

graph LR
    A[源文件] --> B[路由生成器]
    B --> C[routeTree.gen.ts]
    C --> D[类型定义]
    B --> E[路由映射]

生成器接受用户定义的路由结构,输出包含以下内容:

  • 完整的类型定义
  • 路由树结构
  • 路径参数类型推断
  • 搜索参数类型安全

Vite 插件

router-plugin 集成了 Vite 构建工具,提供开发时的路由热更新支持:资料来源:packages/router-plugin/src/vite.ts

功能描述
路由代码生成构建时生成路由类型
热模块替换路由变化时无刷新更新
路径别名支持 @~ 路径别名
预加载指令生成预加载指令代码

状态管理

TanStack Router 采用响应式状态管理,通过 stores 模式管理路由状态:

graph TD
    A[用户导航] --> B[Router.navigate]
    B --> C[更新 URL]
    C --> D[匹配路由]
    D --> E[加载路由数据]
    E --> F[更新 Matches Store]
    F --> G[触发组件重渲染]

Store 架构

Store用途数据类型
matches当前匹配的路由RouteMatch[]
location当前路径信息ParsedLocation
preloadPromise预加载状态Promise<void>

资料来源:packages/router-core/src/stores.ts

框架适配层

React Router

react-router 包提供了 React 框架的完整适配:资料来源:packages/react-router/src/index.tsx

  • useRouter - 获取路由器实例
  • RouterProvider - 根组件
  • Link - 声明式导航
  • useNavigate - 编程式导航
  • useParams - 获取路径参数
  • useSearch - 获取搜索参数

Solid Router

solid-router 包为 SolidJS 提供原生适配,遵循相同的 API 设计模式。

TanStack Start

TanStack Start 是完整的应用框架,包含:

  • SSR/Streaming 支持
  • 服务函数(Server Functions)
  • API 路由
  • Vite 驱动的构建系统

路由匹配流程

graph TD
    A[解析 URL] --> B[查找扁平匹配]
    B --> C{匹配成功?}
    C -->|是| D[构建路由分支]
    C -->|否| E[返回 404]
    D --> F[处理路由遮罩]
    F --> G[加载匹配数据]
    G --> H[渲染组件]

关键函数

函数文件位置职责
processRouteTreenew-process-route-tree.ts处理完整路由树
findFlatMatchnew-process-route-tree.ts查找扁平化匹配
buildRouteBranchnew-process-route-tree.ts构建路由分支
processRouteMasksnew-process-route-tree.ts处理路由遮罩

资料来源:packages/router-core/src/new-process-route-tree.ts

性能优化

TanStack Router 通过多种机制优化性能:

1. LRU 缓存

使用 LRU(最近最少使用)缓存路由匹配结果,减少重复计算:

import { createLRUCache } from './lru-cache'

2. 深度相等比较

提供多种相等性检查函数,避免不必要的更新:

  • deepEqual - 深度对象比较
  • replaceEqualDeep - 替换相等的深度值
  • nullReplaceEqualDeep - null 感知的深度比较

3. 路由预加载

支持预加载路由代码和数据,提升导航体验:资料来源:packages/router-core/src/load-matches.ts

export const routeNeedsPreload = (/* ... */)
export const loadRouteChunk = (/* ... */)
export const loadMatches = (/* ... */)

社区关注点

基于社区反馈,以下性能相关问题值得关注:

认证模式性能

Issue #3997 报告了推荐认证模式的性能问题:onBeforeLoad 在每次页面导航时都会执行,即使在已认证的情况下也不例外。资料来源:社区讨论 #3997

样式加载

Issue #3023 涉及 CSS Modules 与 TanStack Start 的集成问题,样式仅在客户端加载导致 FOUC(无样式内容闪烁)。资料来源:社区讨论 #3023

可访问性

Issue #918 指出缺乏路由变化时的焦点管理和 ARIA 公告,这是 1.0 版本承诺但未完全实现的功能。资料来源:社区讨论 #918

包依赖关系

@tanstack/router-core
├── @tanstack/history
├── @tanstack/loading
└── 工具函数

@tanstack/react-router
├── @tanstack/router-core
└── react

@tanstack/solid-router
├── @tanstack/router-core
└── solid-js

@tanstack/start
├── @tanstack/react-router
├── @tanstack/start-server-core
└── vite

总结

TanStack Router 的核心包架构体现了以下设计原则:

  1. 关注点分离 - 核心逻辑与框架实现分离
  2. 类型安全 - 代码生成提供完整类型推断
  3. 性能优先 - LRU 缓存、深度比较优化
  4. 响应式设计 - Store 模式管理状态
  5. 模块化 - 清晰的依赖层次结构

这套架构使得 TanStack Router 能够同时支持多个前端框架,同时保持一致的功能和 API 体验。

资料来源:packages/router-core/src/stores.ts

核心概念与类型系统

TanStack Router 是一个功能强大且类型安全的路由解决方案,为 React 和 Solid 提供统一的路由管理能力。本页面深入解析其核心概念、类型系统以及关键架构设计。

章节 相关页面

继续阅读本节完整说明和来源证据。

章节 路由树的构建与处理

继续阅读本节完整说明和来源证据。

章节 路由匹配机制

继续阅读本节完整说明和来源证据。

章节 核心类型定义

继续阅读本节完整说明和来源证据。

路由系统架构

路由树的构建与处理

TanStack Router 的核心是路由树(Route Tree)机制。路由树是一个嵌套的、层次化的数据结构,定义了应用程序的所有路由路径及其相互关系。

graph TD
    A[根路由 /] --> B[博客路由 /blog]
    A --> C[关于路由 /about]
    B --> D[文章路由 /blog/:postId]
    B --> E[分类路由 /blog/category/:categoryId]
    D --> F[评论路由 /blog/:postId/comments/:commentId]

路由树的处理流程由 processRouteTree 函数完成,负责解析路由配置、构建路由分支、处理路由掩码等核心工作。资料来源:packages/router-core/src/new-process-route-tree.ts:1-50

路由匹配机制

路由匹配是 TanStack Router 类型安全的核心保障。当用户导航到一个 URL 时,路由器需要:

  1. 路径解析:将 URL 转换为内部路径表示
  2. 分支匹配:在路由树中查找匹配的路由分支
  3. 参数提取:从 URL 中提取路径参数和查询参数
  4. 类型推断:基于匹配的路由推断正确的类型

匹配过程涉及三个核心函数:

函数职责资料来源
findFlatMatch在扁平路由列表中查找匹配packages/router-core/src/new-process-route-tree.ts:100-150
findRouteMatch在路由树中递归查找匹配packages/router-core/src/new-process-route-tree.ts:150-200
findSingleMatch处理单个路由的精确匹配packages/router-core/src/new-process-route-tree.ts:200-250

类型系统深度解析

核心类型定义

TanStack Router 的类型系统构建在几个关键接口之上:

// 路由标识符
interface RouteId {
  id: string
  path: string
  fullPath: string
}

// 路由匹配结果
interface RouteMatch {
  id: string
  routeId: string
  params: Record<string, string>
  search: Record<string, unknown>
  hash: string
  href: string
  isExact: boolean
  route: AnyRoute
}

资料来源:packages/router-core/src/Matches.ts:1-30

泛型类型推断

TanStack Router 大量使用 TypeScript 的泛型和条件类型来实现类型安全:

// 路由选项的泛型定义
interface RouteOptions<
  TParentRoute extends AnyRoute = AnyRoute,
  TPath extends string = string,
> {
  path?: TPath
  component?: Component
  beforeLoad?: BeforeLoadFn<TParentRoute, TPath>
  loader?: LoaderFn<TParentRoute, TPath>
  validateSearch?: SearchSchema<TPath>
}

// 搜索参数的验证模式
type SearchSchema<TPath extends string> = {
  [K in keyof ParseSearchParams<TPath>]: z.ZodType | [z.ZodType, string]
}

资料来源:packages/router-core/src/route.ts:50-150

深度相等比较

类型系统中包含深度相等比较的实用工具,用于判断复杂对象的相等性:

// 深度相等比较函数
export function deepEqual<T>(a: T, b: T): boolean

// 针对深度相等的 null 安全版本
export function nullReplaceEqualDeep<T>(obj: T): T

这些函数在路由匹配比较和状态更新中发挥重要作用,确保只有在真正发生变化时才触发重新渲染。

查询参数系统

搜索参数的编码与解析

TanStack Router 提供了强大的查询参数处理能力,支持类型安全的搜索参数管理。

graph LR
    A[用户输入] --> B[validateSearch 验证]
    B --> C[ParseSearchParams 类型转换]
    C --> D[URLSearchParams 编码]
    D --> E[URL 字符串]

编码函数(源自 qss 包的重实现):

export function encode(
  obj: Record<string, any>,
  stringify: (value: any) => string = String,
): string {
  const result = new URLSearchParams()
  for (const key in obj) {
    const val = obj[key]
    if (val !== undefined) {
      result.set(key, stringify(val))
    }
  }
  return result.toString()
}

资料来源:packages/router-core/src/qss.ts:20-40

默认解析器与序列化器

TanStack Router 提供开箱即用的搜索参数解析和序列化功能:

功能默认实现可配置项
解析defaultParseSearch自定义 SearchParser
序列化defaultStringifySearch自定义 SearchSerializer
// 类型定义
type SearchParser = (search: string) => Record<string, unknown>
type SearchSerializer = (search: Record<string, unknown>) => string

路由器核心架构

路由器初始化流程

sequenceDiagram
    participant U as 用户代码
    participant R as Router
    participant H as History
    participant S as Stores
    
    U->>R: createRouter({ routeTree })
    R->>R: processRouteTree(routeTree)
    R->>R: buildRouteBranch()
    R->>S: 创建状态存储
    R->>H: 初始化浏览器历史
    R->>R: 注册导航监听器

路由加载机制

路由加载是 TanStack Router 高性能的关键。路由器支持延迟加载、预加载和流式加载:

// 加载路由块
async function loadRouteChunk(routeId: string): Promise<void>

// 检查是否需要预加载
function routeNeedsPreload(routeId: string): boolean

// 批量加载匹配
async function loadMatches(
  router: Router,
  matches: RouteMatch[],
  opts?: LoadOptions
): Promise<void>

资料来源:packages/router-core/src/load-matches.ts:1-80

滚动恢复

TanStack Router 内置滚动位置恢复功能,通过 setupScrollRestoration 管理浏览器的滚动状态:

// 记录滚动位置
function setupScrollRestoration(router: Router): void

// 保存位置到历史记录
scrollRestoration?: 'manual' | 'auto'

状态管理

路由器存储系统

TanStack Router 使用响应式存储系统管理路由状态:

graph TD
    S[Stores] --> M[matches 存储]
    S --> P[params 存储]
    S --> Q[search 存储]
    S --> L[location 存储]
    M --> R[React/Solid 组件]
    P --> R
    Q --> R
    L --> R

核心存储列表

存储名称类型用途
matchesStore<RouteMatch[]>当前匹配的路由列表
paramsStore<Record<string, string>>路径参数
searchStore<Record<string, unknown>>查询参数
locationStore<Location>完整位置信息

状态更新流程

路由器状态更新遵循以下流程:

  1. 历史变化检测:监听 popstatepushStatereplaceState 事件
  2. 路由匹配计算:调用 findRouteMatch 计算新路径的匹配结果
  3. 加载器执行:按需执行路由的 loader 函数
  4. 状态同步:更新内部存储,触发响应式更新
  5. UI 更新:React/Solid 组件自动重新渲染

常见问题与最佳实践

认证模式性能考量

根据社区反馈,使用推荐的认证模式时可能遇到性能问题:

问题 #3997:使用推荐的认证模式时性能较差,因为 onBeforeLoad 在每次页面导航时都会执行。

优化建议

  • 使用 beforeLoad 时返回稳定的引用,避免不必要的重新渲染
  • 利用 cache() 缓存认证检查结果
  • 考虑在顶层路由使用 loader 而非 beforeLoad

可访问性支持

TanStack Router v1 承诺了可访问性支持,但目前仍存在改进空间:

问题 #918:路由切换时缺少焦点管理和屏幕阅读器公告。

当前支持

  • HeadContent 组件用于管理文档头部内容
  • 自动更新 <title> 和元信息

缺失功能

  • 路由切换时的焦点管理
  • ARIA live regions 公告

CSS Modules 与 SSR

在 TanStack Start 中使用 CSS Modules 时可能遇到样式闪烁(FOUC)问题:

问题 #3023:样式只在客户端加载,导致初始渲染时出现未样式化内容闪烁。

解决方案

  • 配置 Vite 的 CSS 模块处理
  • 使用服务端渲染(SSR)确保样式在首屏可用

扩展阅读

主题说明
路由配置详细了解路由配置选项
搜索参数深入理解搜索参数验证与转换
加载器掌握数据加载模式
重写与重定向URL 重写与重定向配置

资料来源:packages/router-core/src/Matches.ts:1-30

数据加载与状态管理

TanStack Router 提供了一套完整的数据加载与状态管理体系,旨在为现代 Web 应用提供类型安全的数据获取、状态管理和路由同步能力。该系统核心设计理念包括:

章节 相关页面

继续阅读本节完整说明和来源证据。

章节 核心组件关系

继续阅读本节完整说明和来源证据。

章节 Loader 的定义与注册

继续阅读本节完整说明和来源证据。

章节 数据加载执行流程

继续阅读本节完整说明和来源证据。

概述

TanStack Router 提供了一套完整的数据加载与状态管理体系,旨在为现代 Web 应用提供类型安全的数据获取、状态管理和路由同步能力。该系统核心设计理念包括:

  • 端到端类型安全:从路由参数到 loader 数据,整个数据流都具备完整的 TypeScript 类型推导
  • 声明式数据加载:通过 route loader 模式,将数据获取逻辑与路由定义紧密绑定
  • 统一的状态管理:利用响应式 store 机制,实现路由状态与应用状态的无缝集成
  • 灵活的数据转换:支持自定义搜索参数的解析和序列化逻辑

该系统主要涉及以下核心模块:

模块职责核心文件
Loader 系统路由级数据获取与缓存load-matches.ts
搜索参数处理URL 查询字符串的类型化解析与序列化searchParams.ts
状态存储响应式状态管理stores.ts
路径与 URL 处理路径插值与 URL 构建path.tsqss.ts
延迟数据支持 deferred 响应模式defer.ts

架构设计

TanStack Router 的数据加载架构遵循以下核心流程:

graph TD
    A[路由配置定义] --> B[Loader 函数注册]
    B --> C[路由匹配解析]
    C --> D[数据加载触发]
    D --> E{加载模式}
    E -->|同步| F[直接返回数据]
    E -->|异步| G[Promise/Deferred]
    E -->|流式| H[Streaming Response]
    F --> I[状态存储更新]
    G --> I
    H --> I
    I --> J[组件响应式获取]
    J --> K[UI 渲染]
    
    L[URL 变化] --> C
    M[搜索参数变化] --> N[Search Params 解析] --> C

核心组件关系

┌─────────────────────────────────────────────────────────────┐
│                      Router Instance                        │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │   Router    │  │   Matches   │  │   Loader Data       │  │
│  │   Options   │──│   Store     │──│   Store             │  │
│  └─────────────┘  └─────────────┘  └─────────────────────┘  │
│         │                │                    │              │
│         ▼                ▼                    ▼              │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │  History    │  │   Route     │  │   Loader            │  │
│  │  Manager    │  │   Tree      │  │   Executors         │  │
│  └─────────────┘  └─────────────┘  └─────────────────────┘  │
└─────────────────────────────────────────────────────────────┘

Loader 系统

Loader 的定义与注册

Loader 是 TanStack Router 中用于在路由匹配时获取数据的核心机制。每个路由可以定义自己的 loader 函数,该函数在路由激活时自动执行:

// 典型的 Loader 定义
const routeConfig = {
  path: '/users/:userId',
  loader: async ({ params, search }) => {
    const userId = params.userId
    return fetchUser(userId)
  },
  component: UserComponent
}

数据加载执行流程

数据加载由 load-matches.ts 模块中的 loadMatches 函数协调执行:

sequenceDiagram
    participant Route as 路由配置
    participant Loader as Loader 执行器
    participant Store as 状态存储
    participant Cache as 缓存系统
    participant Component as 组件
    
    Route->>Loader: 路由匹配成功
    Loader->>Cache: 检查缓存
    alt 缓存命中
        Cache-->>Loader: 返回缓存数据
    else 缓存未命中
        Loader->>Loader: 执行 loader 函数
        Loader->>Store: 更新 loaderData store
    end
    Store-->>Component: 通知数据更新
    Component->>Store: 订阅并读取数据

加载条件判断

routeNeedsPreload 函数决定是否需要重新加载数据:

// packages/router-core/src/load-matches.ts
export function routeNeedsPreload(
  existingMatch: RouteMatch,
  newMatch: RouteMatch,
): boolean {
  // 路由 ID 不同则需要重新加载
  if (existingMatch.id !== newMatch.id) {
    return true
  }
  
  // 路径参数变化需要重新加载
  if (!deepEqual(existingMatch.params, newMatch.params)) {
    return true
  }
  
  // 搜索参数变化需要重新加载
  if (!deepEqual(existingMatch.search, newMatch.search)) {
    return true
  }
  
  return false
}

路由分块加载

TanStack Router 支持按需加载路由代码块,通过 loadRouteChunk 实现:

// packages/router-core/src/load-matches.ts
export async function loadRouteChunk(
  route: AnyRoute,
): Promise<LoadedRoute> {
  // 动态导入路由模块
  const module = await route.options.loadComponent?.()
  
  return {
    component: module.default,
    beforeLoad: module.beforeLoad,
    loader: module.loader,
    pendingComponent: module.pendingComponent,
    errorComponent: module.errorComponent
  }
}

搜索参数处理

搜索参数解析

搜索参数处理由 searchParams.ts 模块负责,提供默认的解析和序列化实现:

// packages/router-core/src/searchParams.ts

export const defaultParseSearch: SearchParser = (searchStr) => {
  if (!searchStr || searchStr === '?') {
    return {}
  }
  
  const searchParams = new URLSearchParams(searchStr)
  const parsed: Record<string, any> = {}
  
  searchParams.forEach((value, key) => {
    parsed[key] = toValue(value)
  })
  
  return parsed
}

export const defaultStringifySearch: SearchSerializer = (search) => {
  if (!search || !hasKeys(search)) {
    return ''
  }
  return '?' + encode(search)
}

类型安全的搜索参数

TanStack Router 支持通过 Zod 等 schema 库进行搜索参数的类型验证:

import { z } from 'zod'

const userSearchSchema = z.object({
  page: z.coerce.number().min(1).default(1),
  limit: z.coerce.number().min(1).max(100).default(20),
  sort: z.enum(['asc', 'desc']).optional(),
  filter: z.string().optional()
})

const routeConfig = {
  path: '/users',
  validateSearch: userSearchSchema,
  loader: async ({ search }) => {
    // search 参数已通过 schema 验证并具有完整类型
    const { page, limit, sort, filter } = search
    return fetchUsers({ page, limit, sort, filter })
  }
}

自定义序列化逻辑

通过自定义 stringify 函数,可以控制搜索参数的序列化行为:

// packages/router-core/src/qss.ts

export function encode(
  obj: Record<string, any>,
  stringify: (value: any) => string = String
): string {
  const result = new URLSearchParams()
  
  for (const key in obj) {
    const val = obj[key]
    if (val !== undefined) {
      result.set(key, stringify(val))
    }
  }
  
  return result.toString()
}

状态管理机制

Store 架构

TanStack Router 使用响应式 store 模式管理路由状态:

graph LR
    A[History API] --> B[Router Store]
    B --> C[Matches Store]
    C --> D[Loader Data Store]
    D --> E[Search Params Store]
    
    F[用户交互] --> A
    G[导航操作] --> B

核心 Store 类型

Store 类型用途关键操作
matchesStore存储当前匹配的路由列表get(), update()
loaderDataStore存储各路由的 loader 返回数据get(), set(), invalidate()
latestEvictedMatchStore存储被驱逐的路由匹配(用于缓存)get()
globalFilterStore全局搜索过滤器get(), set()
outletContextStore路由上下文传递get(), set()

状态更新流程

sequenceDiagram
    participant Nav as 导航操作
    participant Router as 路由实例
    participant Matches as Matches Store
    participant Loader as Loader Data Store
    participant React as React 组件
    
    Nav->>Router: navigate() 或 Link 点击
    Router->>Router: 解析新路径
    Router->>Matches: 更新匹配路由
    Matches-->>Router: 匹配更新完成
    Router->>Loader: 触发 loader 执行
    Loader-->>Loader: 异步数据获取
    Loader->>LoaderData: 更新 loader 数据
    LoaderData-->>React: 通知订阅者
    React->>React: 重新渲染

延迟数据(Deferred)

Deferred 机制概述

TanStack Router 支持 React 的 defer 模式,允许部分数据立即可用而其他数据异步加载:

// packages/router-core/src/defer.ts

export function defer<T>(data: T): DeferredData {
  return {
    type: 'deferred',
    data
  }
}

export function isDeferred(val: any): val is DeferredData {
  return val?.type === 'deferred'
}

流式加载支持

Deferred 数据支持流式传输,在 React 18+ 环境中提供更好的用户体验:

const routeConfig = {
  loader: async () => {
    return defer({
      // 立即可用的数据
      user: await fetchUser(userId),
      // 延迟加载的数据
      posts: fetchPosts(userId) // 这将使用 Suspense 流式传输
    })
  }
}

缓存与性能优化

LRU 缓存机制

TanStack Router 内置 LRU 缓存用于存储已加载的路由数据和分块:

// packages/router-core/src/lru-cache.ts

export class LRUCache<K, V> {
  private cache: Map<K, V>
  
  constructor(private maxSize: number) {
    this.cache = new Map()
  }
  
  get(key: K): V | undefined {
    const value = this.cache.get(key)
    if (value !== undefined) {
      // 移到末尾(最近使用)
      this.cache.delete(key)
      this.cache.set(key, value)
    }
    return value
  }
  
  set(key: K, value: V): void {
    if (this.cache.has(key)) {
      this.cache.delete(key)
    } else if (this.cache.size >= this.maxSize) {
      // 删除最久未使用的项
      const firstKey = this.cache.keys().next().value
      this.cache.delete(firstKey)
    }
    this.cache.set(key, value)
  }
}

预加载策略

策略描述触发条件
悬停预加载链接悬停时预加载目标路由数据preloadOnHover 选项
可见预加载链接进入视口时预加载需配合 Intersection Observer
预测预加载基于导航历史预测并预加载preloadAmbiguousRoutes

错误处理与边界

加载错误处理

Loader 执行过程中的错误通过统一的错误边界机制处理:

const routeConfig = {
  loader: async ({ throw }) => {
    const data = await fetchData()
    
    if (!data) {
      throw redirect({ to: '/error-page' })
    }
    
    return data
  },
  errorComponent: ErrorBoundary
}

社区相关问题

性能问题:Issue #3997 报告了在使用推荐的身份验证模式时性能下降的问题。具体表现为 _root_authed 路由的 onBeforeLoad 在每次页面导航时都会执行,即使用户已经认证。这与 loader 的执行时机和缓存策略有关。

可访问性问题:Issue #918 指出路由变化时缺乏适当的焦点管理和语义化宣告,影响屏幕阅读器用户的使用体验。

最佳实践

数据加载模式

  1. 集中式数据获取:在路由级别定义 loader,避免在组件中直接请求数据
  2. 类型安全的参数:使用 schema 验证所有外部输入(params、search)
  3. 合理的缓存策略:根据数据更新频率配置适当的缓存时长
  4. 错误边界设计:为每个路由配置适当的错误处理组件

状态管理建议

场景推荐方案
路由级数据使用 route loader
全局应用状态使用 TanStack Store
表单状态使用 TanStack Form
服务端状态/缓存使用 TanStack Query

代码示例

import { createRouter, createRoute } from '@tanstack/react-router'
import { z } from 'zod'

// 定义搜索参数 schema
const searchSchema = z.object({
  page: z.coerce.number().min(1).default(1),
  category: z.string().optional()
})

// 创建用户列表路由
const usersRoute = createRoute({
  getParent: () => rootRoute,
  path: '/users',
  validateSearch: searchSchema,
  loader: async ({ context, search }) => {
    const users = await context.queryClient.fetchQuery({
      queryKey: ['users', search],
      queryFn: () => fetchUsers(search)
    })
    return { users }
  },
  component: UsersPage
})

相关配置选项

选项类型默认值说明
loaderGcTimenumber300000Loader 数据缓存时间(毫秒)
staleTimenumber0数据被认为过期的时长
preloadOnHoverbooleantrue悬停时是否预加载
defaultPreload`'intent' \'render' \false`'intent'默认预加载策略
defaultPreloadDelaynumber50预加载延迟(毫秒)

来源:https://github.com/TanStack/router / 项目说明书

Search Params 验证与序列化

Search Params(搜索参数)验证与序列化是 TanStack Router 中处理 URL 查询字符串的核心功能模块。该模块负责在路由导航时将 TypeScript 对象与 URL 查询字符串之间进行双向转换,并提供中间件机制来控制搜索参数的传递行为。

章节 相关页面

继续阅读本节完整说明和来源证据。

章节 搜索参数类型定义

继续阅读本节完整说明和来源证据。

章节 searchParams.ts

继续阅读本节完整说明和来源证据。

章节 qss.ts 编码模块

继续阅读本节完整说明和来源证据。

核心概念

搜索参数类型定义

TanStack Router 通过类型系统定义了搜索参数的结构和验证规则。核心类型包括:

类型名称作用源码位置
SearchSerializer将搜索对象序列化为字符串router.ts:18
SearchParser将字符串解析为搜索对象router.ts:25
SearchMiddleware拦截并修改搜索参数的中间件route.ts
AnySchema通用的 Schema 类型定义index.ts

搜索参数的验证与序列化流程如下:

graph TD
    A[URL Query String] --> B[parseSearch 解析]
    B --> C[Search Schema 验证]
    C --> D{验证通过?}
    D -->|是| E[Route Match Search]
    D -->|否| F[Error Handling]
    E --> G[navigation/link 生成]
    G --> H[stringifySearch 序列化]
    H --> I[URL Query String]

默认序列化实现

searchParams.ts

TanStack Router 提供了默认的搜索参数解析和序列化实现:

// packages/router-core/src/searchParams.ts
export function defaultParseSearch(search: string) {
  // 将 URL 查询字符串解析为对象
}

export function defaultStringifySearch(search: Record<string, any>): string {
  // 将对象序列化为 URL 查询字符串
}

qss.ts 编码模块

qss.ts 是对 qss 包的重新实现,使用现代浏览器 API(URLSearchParams)进行编码:

// packages/router-core/src/qss.ts
export function encode(
  obj: Record<string, any>,
  stringify: (value: any) => string = String,
): string {
  const result = new URLSearchParams()

  for (const key in obj) {
    const val = obj[key]
    if (val !== undefined) {
      result.set(key, stringify(val))
    }
  }

  return result.toString()
}

关键特性:

  • 使用 URLSearchParams API 确保浏览器兼容性
  • 单值编码(每个 key 只保留一个值)
  • 忽略 undefined
  • 支持自定义字符串化函数 资料来源:qss.ts:28-45

Router 配置

在创建 Router 时,可以通过选项配置搜索参数的解析和序列化行为:

// packages/router-core/src/router.ts
interface RouterOptions {
  /**
   * 生成链接时用于字符串化搜索参数的函数
   */
  stringifySearch?: SearchSerializer
  
  /**
   * 解析当前位置时用于解析搜索参数的函数
   */
  parseSearch?: SearchParser
}

资料来源:router.ts:15-28

默认配置:

stringifySearch: options.stringifySearch ?? defaultStringifySearch,
parseSearch: options.parseSearch ?? defaultParseSearch,

资料来源:router.ts:63-64

搜索参数中间件

TanStack Router 提供了两种内置的搜索参数中间件,用于控制导航时搜索参数的行为。

stripSearchParams - 移除搜索参数

stripSearchParams 中间件用于从导航中移除可选或默认值的搜索参数:

// packages/router-core/src/searchMiddleware.ts
export function stripSearchParams<
  TSearchSchema,
  TOptionalProps = PickOptional<NoInfer<TSearchSchema>>,
  const TValues =
    | Partial<NoInfer<TOptionalProps>>
    | Array<keyof TOptionalProps>,
  const TInput = IsRequiredParams<TSearchSchema> extends never
    ? TValues | true
    : TValues,
>(input: NoInfer<TInput>): SearchMiddleware<TSearchSchema>

使用场景:

输入类型行为
true移除所有搜索参数(仅当没有必需参数时)
Array of keys始终移除指定的 keys
Object of defaults移除与默认值相等的 keys

使用示例:

const searchOptions = {
  middlewares: [
    // 移除所有等于默认值的搜索参数
    stripSearchParams({
      page: 1,
      limit: 20
    }),
    // 或者只移除特定参数
    stripSearchParams(['page', 'limit'])
  ]
}

资料来源:searchMiddleware.ts:17-47

retainSearchParams - 保留搜索参数

retainSearchParams 中间件用于在导航时保留指定的搜索参数:

// packages/router-core/src/searchMiddleware.ts
export function retainSearchParams<TSearchSchema extends object>(
  keys: Array<keyof TSearchSchema> | true,
): SearchMiddleware<TSearchSchema>

参数说明:

参数值行为
true保留所有当前搜索参数
Array of keys只复制指定的 keys 到目标搜索参数

使用示例:

const searchOptions = {
  middlewares: [
    // 导航时保留 page 和 sort 参数
    retainSearchParams(['page', 'sort'])
  ]
}

资料来源:searchMiddleware.ts:76-95

中间件工作流程

sequenceDiagram
    participant Link as Link/Navigation
    participant Middleware as Search Middleware
    participant Route as Route Search Schema
    
    Link->>Middleware: 传入 search 参数
    Middleware->>Middleware: 执行中间件逻辑
    alt stripSearchParams
        Middleware->>Middleware: 删除指定参数
    end
    alt retainSearchParams
        Middleware->>Route: 合并当前 search
    end
    Middleware->>Route: 输出处理后的 search

搜索参数验证适配器

TanStack Router 通过适配器模式支持多种 Schema 验证库,实现类型安全的搜索参数验证。

架构概览

graph TD
    A[Search Schema Definition] --> B[Validator Adapter]
    B --> C[Validation Library]
    C -->|成功| D[Valid Search Object]
    C -->|失败| E[Validation Error]
    
    B --> F[Zod Adapter]
    B --> G[Valibot Adapter]
    B --> H[Arktype Adapter]

核心类型定义

类型名称说明源码位置
Validator验证器接口router-core/index.ts
ValidatorAdapter适配器基类router-core/index.ts
AnyValidator通用的验证器类型router-core/index.ts
ValidatorFn验证函数类型router-core/index.ts

核心验证器接口:

export interface ValidatorAdapter<
  TInput,
  TOutput,
  TSchema = any,
> {
  validate(params: {
    schema: TSchema
    value: TInput
    parser: SearchParser
    stringify: SearchSerializer
  }): Promise<{
    value?: TOutput
    error?: unknown
  }>
}

Zod 适配器

Zod 是最广泛使用的 TypeScript 优先的模式验证库。TanStack Router 通过 @tanstack/zod-adapter 提供集成:

// packages/zod-adapter/src/index.ts
import { z } from 'zod'
import { createZodValidator } from '@tanstack/zod-adapter'

const searchSchema = z.object({
  page: z.coerce.number().int().positive().default(1),
  limit: z.coerce.number().int().positive().max(100).default(20),
  query: z.string().optional(),
})

const route = new Route({
  path: '/search',
  validateSearch: createZodValidator(searchSchema),
})

资料来源:zod-adapter/src/index.ts

Valibot 适配器

Valibot 是一个轻量级的模式验证库,支持 tree-shaking:

// packages/valibot-adapter/src/index.ts
import * as v from 'valibot'
import { createValibotValidator } from '@tanstack/valibot-adapter'

const searchSchema = v.object({
  page: v.pipe(v.number(), v.integer(), v.minValue(1)),
  limit: v.pipe(v.number(), v.integer(), v.maxValue(100)),
  query: v.optional(v.string()),
})

const route = new Route({
  path: '/search',
  validateSearch: createValibotValidator(searchSchema),
})

资料来源:valibot-adapter/src/index.ts

Arktype 适配器

Arktype 是一个基于 TypeScript 类型推断的验证库:

// packages/arktype-adapter/src/index.ts
import arktype from 'arktype'
import { createArktypeValidator } from '@tanstack/arktype-adapter'

const searchSchema = arktype({
  page: "[email protected]",
  limit: "[email protected]<101",
  query: "string?",
})

const route = new Route({
  path: '/search',
  validateSearch: createArktypeValidator(searchSchema),
})

资料来源:arktype-adapter/src/index.ts

验证适配器对比

特性ZodValibotArktype
包大小~38kB~14kB~15kB
Tree-shaking部分支持完全支持完全支持
TypeScript 类型推断最强
默认值支持原生支持需要使用 optional()原生支持
类型转换z.coercepipe()@ 操作符

实际使用示例

基础搜索路由

import { createRouter, createRoute } from '@tanstack/react-router'
import { createZodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'

const searchSchema = z.object({
  page: z.coerce.number().int().min(1).default(1),
  limit: z.coerce.number().int().min(1).max(100).default(20),
  sort: z.enum(['asc', 'desc']).optional(),
  filter: z.string().optional(),
})

const indexRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/',
  validateSearch: createZodValidator(searchSchema),
})

带中间件的搜索路由

const searchRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: '/search',
  validateSearch: createZodValidator(searchSchema),
  search: {
    middlewares: [
      // 导航时保留分页参数
      retainSearchParams(['page', 'limit']),
      // 移除等于默认值的搜索参数
      stripSearchParams({
        page: 1,
        limit: 20,
        sort: 'asc',
      }),
    ],
  },
})

导航时传递搜索参数

import { useNavigate, useRouter } from '@tanstack/react-router'

function SearchComponent() {
  const navigate = useNavigate()
  
  const handleSearch = (query: string) => {
    navigate({
      to: '/search',
      search: {
        query,
        page: 1,
        limit: 20,
      },
    })
  }
}

社区相关问题

根据社区反馈,搜索参数功能相关的常见问题包括:

  1. CSS Modules 问题 (#3023) - 部分用户在使用 TanStack Start 时遇到样式加载问题,可能与 SSR 时的搜索参数处理相关
  1. 认证模式性能问题 (#3997) - 使用推荐的身份验证模式时,beforeLoad 钩子在每个页面导航时都会运行,可能影响包含复杂搜索参数验证的路由性能
  1. 链接点击后内容未加载 (#597) - 某些情况下链接路径改变但页面内容未加载,可能与搜索参数解析错误相关

最佳实践

  1. 使用类型安全的 Schema 验证
  • 推荐使用 Zod、Valibot 或 Arktype 定义搜索参数 Schema
  • 利用 TypeScript 推断获得完整的类型提示
  1. 合理使用中间件
  • 使用 stripSearchParams 清理 URL,保持简洁
  • 使用 retainSearchParams 保持重要的上下文参数
  1. 设置合理的默认值
  • 在 Schema 中使用 .default() 设置默认值
  • 避免 URL 中出现冗余参数
  1. 性能考虑
  • 对于高频导航的路由,避免使用过于复杂的验证逻辑
  • 考虑使用 retainSearchParams 减少不必要的参数传递

相关文档

资料来源:router.ts:15-28

认证模式与性能优化

TanStack Router 提供了灵活且类型安全的认证路由模式,允许开发者实现基于路由的访问控制。该框架通过路由层级结构、beforeLoad 生命周期钩子以及 Router Context 来管理认证状态和权限验证。

章节 相关页面

继续阅读本节完整说明和来源证据。

章节 核心组件

继续阅读本节完整说明和来源证据。

章节 基础认证布局

继续阅读本节完整说明和来源证据。

章节 TanStack Start 认证模式

继续阅读本节完整说明和来源证据。

概述

TanStack Router 提供了灵活且类型安全的认证路由模式,允许开发者实现基于路由的访问控制。该框架通过路由层级结构、beforeLoad 生命周期钩子以及 Router Context 来管理认证状态和权限验证。

认证模式架构

TanStack Router 的认证系统基于以下核心概念构建:

graph TD
    A[用户请求] --> B{路由匹配}
    B --> C[_auth 布局路由]
    C --> D{beforeLoad 检查}
    D -->|已认证| E[加载受保护内容]
    D -->|未认证| F[重定向到登录页]
    E --> G[渲染 _authed 子路由]
    F --> H[重定向目标]

核心组件

组件文件位置功能说明
_auth.tsx布局路由认证状态检查与重定向逻辑
_authed.tsx受保护路由仅允许认证用户访问的内容
Router Contextrouter.ts存储用户认证状态与信息

认证路由实现模式

基础认证布局

TanStack Router 推荐使用下划线前缀的布局路由来实现认证保护。以下是 _auth.tsx 的典型实现:

import { createRootRouteWithContext, Outlet } from '@tanstack/react-router'
import { RouterContext } from '../router'

export const Route = createRootRouteWithContext<RouterContext>()({
  beforeLoad: ({ context, location }) => {
    if (!context.authentication?.isAuthenticated) {
      throw redirect({
        to: '/login',
        search: {
          redirect: location.href,
        },
      })
    }
  },
  component: () => <Outlet />,
})

资料来源:examples/react/authenticated-routes/src/routes/_auth.tsx:1-17

TanStack Start 认证模式

在 TanStack Start 项目中,认证模式略有不同,使用 _authed.tsx 布局:

import { createFileRoute, Outlet } from '@tanstack/react-router'

export const Route = createFileRoute('/_authed')({
  beforeLoad: ({ context }) => {
    if (!context.user) {
      throw new Error('Unauthorized')
    }
  },
  component: () => <Outlet />,
})

资料来源:examples/react/start-basic-auth/src/routes/_authed.tsx:1-12

性能问题与优化

社区反馈的性能问题

根据 GitHub Issue #3997 的反馈,使用推荐的认证模式存在显著的性能问题:

使用 TanStack Start 中的推荐认证模式时,_root_authed 路由的 beforeLoad 在每次页面导航时都会执行,即使用户已经认证。
graph LR
    A[页面导航] --> B[beforeLoad 执行]
    B --> C[认证检查]
    C --> D[用户已认证?]
    D -->|是| E[加载数据]
    D -->|每次都检查| E
    E --> F[渲染完成]
    
    style B fill:#ff6b6b
    style D fill:#feca57

性能瓶颈分析

问题影响原因
重复认证检查每次导航增加延迟beforeLoad 在布局路由层执行
状态重新加载用户体验下降未利用缓存的认证状态
不必要的重定向检查额外计算开销即使认证状态未变也执行

Router Context 配置

正确配置 Router Context 是优化认证性能的关键。Context 应在路由初始化时注入,并在后续导航中复用。

interface RouterContext {
  authentication: {
    isAuthenticated: boolean
    user: User | null
  }
}

资料来源:docs/router/guide/router-context.md

Context 注入优化

// 优化前:每次导航都重新创建 Context
const rootRoute = createRootRoute({
  context: () => ({
    authentication: fetchAuthState()
  })
})

// 优化后:利用现有的认证状态
const rootRoute = createRootRoute({
  context: ({ preloadRoute }) => ({
    authentication: preloadRoute ? 
      getCachedAuthState() : 
      fetchAuthState()
  })
})

性能优化策略

1. 利用 beforeLoad 缓存机制

TanStack Router 的 beforeLoad 钩子支持返回值的缓存,可以通过配置减少不必要的重复执行:

const Route = createRootRoute({
  beforeLoad: async ({ context }) => {
    // 检查缓存
    if (context.cachedAuth) {
      return context.cachedAuth
    }
    return await validateSession()
  },
})

2. 条件式预加载

根据社区讨论,合理使用 defaultPreload 配置可以平衡首屏性能和后续导航:

const router = createRouter({
  defaultPreload: false, // 避免不必要的预加载
  stringifySearch: defaultStringifySearch,
  parseSearch: defaultParseSearch,
})

资料来源:packages/router-core/src/router.ts:1-100

3. 路由结构优化

将认证检查限制在必要的路由层级,避免在根路由执行全局认证:

graph TD
    A[Root] --> B[_authed 布局]
    B --> C[Dashboard]
    B --> D[Settings]
    B --> E[Profile]
    
    A --> F[Public]
    F --> G[Landing]
    F --> H[Login]
    
    style A fill:#48dbfb
    style B fill:#ff6b6b
    style F fill:#1dd1a1

最佳实践

实践说明收益
最小化 beforeLoad 逻辑仅执行必要的检查减少每次导航开销
缓存认证状态使用 Router Context 存储避免重复 API 调用
分离公共与受保护路由使用独立的路由树减少不必要的匹配
使用重定向搜索参数保留原始意图改善用户体验

相关配置选项

RouterOptions 关键参数

参数类型默认值说明
historyRouterHistoryBrowserHistory历史记录管理
stringifySearchSearchSerializerdefaultStringifySearch搜索参数序列化
parseSearchSearchParserdefaultParseSearch搜索参数解析
defaultPreload`false \'intent' \'viewport'`false预加载策略

总结

TanStack Router 的认证模式通过布局路由和 beforeLoad 钩子提供了强大且类型安全的访问控制能力。然而,在大规模应用中需要特别注意性能优化,包括合理利用 Context 缓存、优化路由结构以及避免不必要的重复检查。官方文档建议开发者根据具体使用场景选择合适的预加载策略,以在安全性和性能之间取得平衡。

资料来源:examples/react/authenticated-routes/src/routes/_auth.tsx:1-17

SSR 服务端渲染支持

TanStack Router 提供完整的服务端渲染(SSR)支持,允许在服务器端预渲染路由并向客户端传递脱水状态,实现无缝的水合(Hydration)过程。

章节 相关页面

继续阅读本节完整说明和来源证据。

章节 createRequestHandler 函数

继续阅读本节完整说明和来源证据。

章节 关键实现流程

继续阅读本节完整说明和来源证据。

章节 hydrate 函数

继续阅读本节完整说明和来源证据。

核心架构

SSR 系统由三个主要部分组成:

  • 服务端:处理请求、渲染路由、管理脱水状态
  • 客户端:接收脱水数据、执行水合、接管路由交互
  • RSC 集成:支持 React Server Components 的流式 SSR
graph TD
    A[HTTP Request] --> B[createRequestHandler]
    B --> C[createMemoryHistory]
    C --> D[attachRouterServerSsrUtils]
    D --> E[SSR 路由匹配]
    E --> F[脱水状态序列化]
    F --> G[HTML 响应]
    G --> H[客户端 hydrate]
    H --> I[浏览器路由接管]

服务端请求处理

createRequestHandler 函数

createRequestHandler 是 SSR 的核心入口点,用于创建处理 HTTP 请求的函数:

export function createRequestHandler<TRouter extends AnyRouter>({
  createRouter,
  request,
  getRouterManifest,
}: {
  createRouter: () => TRouter
  request: Request
  getRouterManifest?: () => ServerManifest | Promise<ServerManifest>
}): RequestHandler<TRouter>

该函数接收一个路由器工厂函数和 HTTP 请求对象,返回一个处理函数。

关键实现流程

服务端处理流程包含以下步骤:

  1. 创建路由实例:调用 createRouter() 初始化路由器
  2. 注入 SSR 工具:通过 attachRouterServerSsrUtils 附加服务端工具集
  3. URL 规范化:使用 getNormalizedURL 规范化请求路径
  4. 内存历史创建:使用 createMemoryHistory 创建服务端历史记录
  5. 路由匹配与渲染:执行服务端路由匹配和组件渲染

资料来源:packages/router-core/src/ssr/createRequestHandler.ts:1-50

return async (cb) => {
  const router = createRouter()
  
  attachRouterServerSsrUtils({
    router,
    manifest: await getRouterManifest?.(),
  })

  const { url } = getNormalizedURL(request.url, 'http://localhost')
  const origin = getOrigin(request)
  const href = url.href.replace(url.origin, '')

  const history = createMemoryHistory({
    initialEntries: [href],
  })

  router.update({
    history,
    origin: router.options.origin ?? origin,
  })
  // ...
}

客户端水合机制

hydrate 函数

客户端通过 hydrate 函数接收脱水状态并接管路由:

export function hydrate(router: AnyRouter, opts?: HydrationContext): void

脱水状态结构

服务端在 HTML 中序列化的状态结构:

属性缩写类型说明
iidstring路由匹配 ID
bbeforeLoadContextobjectbeforeLoad 上下文
lloaderDataunknown加载器数据
sstatusnumberHTTP 状态码
ssrssrboolean是否为 SSR 渲染
uupdatedAtnumber更新时间戳
eerrorError错误对象

水合流程

function hydrateMatch(
  match: AnyRouteMatch,
  deyhydratedMatch: DehydratedMatch,
): void {
  match.id = deyhydratedMatch.i
  match.__beforeLoadContext = deyhydratedMatch.b
  match.loaderData = deyhydratedMatch.l
  match.status = deyhydratedMatch.s
  match.ssr = deyhydratedMatch.ssr
  match.updatedAt = deyhydratedMatch.u
  match.error = deyhydratedMatch.e
}

资料来源:packages/router-core/src/ssr/ssr-client.ts:15-30

全局类型定义

客户端水合依赖全局变量存储脱水数据:

declare global {
  interface Window {
    [GLOBAL_TSR]?: TsrSsrGlobal
    [GLOBAL_SEROVAL]?: any
  }
}

资料来源:packages/router-core/src/ssr/ssr-client.ts:10-15

RSC 支持

TanStack Router 通过 @tanstack/react-start-rsc 包提供 React Server Components 支持。

RSC SSR 处理器接口

export interface RscSsrHandler {
  /** 预解码流以进行同步 SSR 渲染 */
  decode: (stream: ServerComponentStream) => Promise<RscDecodeResult>
  
  /** 创建可渲染代理(用于 renderServerComponent) */
  createRenderableProxy: (
    stream: ServerComponentStream,
    decoded: RscDecodeResult,
  ) => any
  
  /** 创建组合代理(用于 createCompositeComponent) */
  createCompositeProxy: (
    stream: ServerComponentStream,
    decoded: RscDecodeResult,
    slotUsagesStream?: ReadableStream<RscSlotUsageEvent>,
  ) => any
}

资料来源:packages/react-start-rsc/src/rscSsrHandler.ts:1-35

解码结果结构

export interface RscDecodeResult {
  tree: unknown
  cssHrefs?: Set<string>
  jsPreloads?: Set<string>
}

Rsbuild SSR 解码实现

Rsbuild 通过 __rspack_rsc_manifest__ 提供客户端引用依赖管理:

declare const __rspack_rsc_manifest__: {
  moduleLoading: ModuleLoading
  serverConsumerModuleMap: ServerConsumerModuleMap
  clientManifest: ClientManifest
}

资料来源:packages/react-start-rsc/src/rsbuild/ssr-decode.ts:1-50

虚拟模块接口

RSC 运行时通过虚拟模块 virtual:tanstack-rsc-runtime 提供:

declare module 'virtual:tanstack-rsc-runtime' {
  export function renderToReadableStream<T>(
    data: T,
    options?: object,
  ): ReadableStream<Uint8Array>
  
  export function createFromReadableStream<T = unknown>(
    stream: ReadableStream<Uint8Array>,
    options?: object,
  ): Promise<T>
  
  export function createTemporaryReferenceSet(): object
  
  export function decodeReply<T = unknown>(
    body: string | FormData,
    options?: object,
  ): Promise<T>
  
  export function loadServerAction(id: string): Promise<(...args: any) => any>
}

资料来源:packages/react-start-rsc/src/global.d.ts:30-55

渲染模式

TanStack Router 支持多种服务端渲染模式:

流式 SSR

通过 renderRouterToStream 实现流式渲染,边渲染边发送内容到客户端:

适用于需要快速首字节时间的应用,支持 React Suspense 的流式能力。

阻塞式 SSR

通过 renderRouterToString 实现完整的服务端渲染后再发送响应:

适用于需要等待所有数据加载完成的场景,确保完整的 HTML 内容。

导出清单

服务端导出

导出项来源说明
createRequestHandlerrouter-core创建 SSR 请求处理器
attachRouterServerSsrUtilsrouter-core附加 SSR 工具集
getNormalizedURLrouter-coreURL 规范化
getOriginrouter-core获取请求来源

客户端导出

导出项来源说明
hydraterouter-core/ssr客户端水合函数
mergeHeadersrouter-core/ssr头部合并工具
jsonrouter-core/ssrJSON 响应助手
TsrSsrGlobalrouter-core/ssr全局类型
DehydratedMatchrouter-core/ssr脱水匹配类型
DehydratedRouterrouter-core/ssr脱水路由类型

常见问题

样式闪烁(FOUC)

社区反馈 CSS Modules 在 TanStack Start 中仅在客户端加载,导致初始渲染闪烁。问题源于样式未在服务端预加载。

解决方向:确保样式资源在 SSR 阶段一并序列化到 HTML 中。

认证模式性能

使用 onBeforeLoad 进行认证检查时,每个页面导航都会触发,导致性能问题。建议将认证检查提升到更粗粒度的层级。

辅助功能

当前路由切换时缺少屏幕阅读器公告和焦点管理,这是 v1 承诺但尚未完全实现的功能。

相关资源

资料来源:packages/router-core/src/ssr/createRequestHandler.ts:1-50

CSS 模块与样式方案

TanStack Start 提供了灵活的 CSS 和样式方案支持,旨在解决服务端渲染(SSR)应用中的样式加载问题,包括样式模块化、CSS 内联、样式预加载等核心功能。该方案确保在服务端和客户端渲染时样式的一致性,避免无样式内容闪烁(FOUC)问题。

章节 CSS 处理流程

继续阅读本节完整说明和来源证据。

章节 样式数据流

继续阅读本节完整说明和来源证据。

章节 工作原理

继续阅读本节完整说明和来源证据。

章节 RSC 中的 CSS 处理

继续阅读本节完整说明和来源证据。

概述

TanStack Start 提供了灵活的 CSS 和样式方案支持,旨在解决服务端渲染(SSR)应用中的样式加载问题,包括样式模块化、CSS 内联、样式预加载等核心功能。该方案确保在服务端和客户端渲染时样式的一致性,避免无样式内容闪烁(FOUC)问题。

社区问题 #3023 反映了用户在使用 CSS Modules 时遇到的典型挑战:样式仅在客户端加载导致初始渲染出现闪烁。该问题获得了 74 条评论,表明样式处理是 TanStack Start 用户关注的核心议题之一。

核心架构

CSS 处理流程

TanStack Start 的 CSS 处理涉及多个组件协同工作,从路由匹配到样式注入:

graph TD
    A[路由渲染] --> B[HeadContent 组件]
    B --> C[useTags 函数]
    C --> D[buildTagsFromMatches]
    D --> E[manifestCssTags]
    E --> F[Link 标签注入]
    
    G[RSC 渲染] --> H[awaitLazyElements]
    H --> I[data-rsc-css-href]
    I --> J[CSS 预加载]

样式数据流

阶段组件/函数职责
路由匹配useTags收集当前路由的元数据
标签构建buildTagsFromMatches构建 head/link/meta/script 标签
CSS 收集manifestCssTags从路由配置中提取 CSS 引用
样式注入HeadContent将样式标签渲染到 HTML head

内联 CSS 机制

工作原理

TanStack Start 通过 inlineCss.ts 插件实现了 CSS 内联功能。该机制从 <link rel="stylesheet" data-rsc-css-href> 元素中收集 CSS href,用于在 HTML head 中预加载样式表。

资料来源:packages/start-plugin-core/src/start-manifest-plugin/inlineCss.ts:1-30

RSC 中的 CSS 处理

在 React Server Components (RSC) 模式下,awaitLazyElements 函数负责处理待处理的懒加载载荷,同时收集 CSS href:

export async function awaitLazyElements(
  tree: unknown,
  cssCollector?: CssHrefCollector,
): Promise<void> {
  for (const payload of findPendingLazyPayloads(
    tree,
    new Set(),
    cssCollector,
  )) {
    await Promise.resolve(payload).catch(() => {})
  }
}

资料来源:packages/react-start-rsc/src/awaitLazyElements.ts:1-20

样式预加载流程

graph LR
    A[RSC Payload] --> B[findPendingLazyPayloads]
    B --> C[CSS Collector]
    C --> D[CssHrefCollector]
    D --> E[Link rel=stylesheet 注入]

样式配置方案

基础 CSS 方案

TanStack Start 支持传统的全局 CSS 文件引入,适用于项目级样式定义:

/* examples/react/start-basic/src/styles/app.css */
:root {
  --color-primary: #0066cc;
  --color-background: #ffffff;
}

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  background: var(--color-background);
}

资料来源:examples/react/start-basic/src/styles/app.css:1-12

Tailwind CSS v4 集成

TanStack Start 完全支持 Tailwind CSS v4,提供开箱即用的集成方案:

npx gitpick TanStack/router/tree/main/examples/react/start-tailwind-v4 start-tailwind-v4

#### 项目结构

start-tailwind-v4/
├── src/
│   ├── routes/
│   │   ├── index.tsx
│   │   └── posts_.$postId.tsx
│   └── styles/
│       └── app.css
├── tailwind.config.ts
└── package.json

资料来源:examples/react/start-tailwind-v4/README.md:1-45

#### Tailwind v4 配置特点

特性说明
CSS-first 配置使用 CSS 文件进行主题配置
零构建时间浏览器原生 CSS 解析
容器查询内置响应式设计支持
样式层级改进的样式优先级处理

HeadContent 组件

核心功能

HeadContent 是 TanStack Router 中负责渲染 head 标签内容的核心组件,它使用 useTags Hook 来构建和管理所有必要的 head 元素:

export const useTags = (assetCrossOrigin?: AssetCrossOriginConfig) => {
  const router = useRouter()
  const nonce = router.options.ssr?.nonce

  if (isServer ?? router.isServer) {
    return buildTagsFromMatches(
      router,
      nonce,
      router.stores.matches.get(),
      assetCrossOrigin,
    )
  }

  const routeMeta = useStore(
    router.stores.matches,
    (matches) => matches.map((match) => match.meta).filter((meta) => meta !== undefined),
    deepEqual,
  )
  // ...
}

资料来源:packages/react-router/src/headContentUtils.tsx:1-45

服务端与客户端差异

环境数据来源渲染方式
服务端buildTagsFromMatches静态构建 HTML
客户端router.stores.matches响应式更新

常见问题与解决方案

问题一:CSS Modules 闪烁 (FOUC)

问题描述:样式仅在客户端加载,初始渲染时出现无样式内容闪烁。

解决方案

  1. 确保 CSS 文件在服务端预加载
  2. 使用 RSC 模式的内联 CSS 机制
  3. 在路由配置中正确声明 CSS 依赖

问题二:样式不匹配

问题描述:服务端和客户端渲染的样式不一致。

排查步骤

graph TD
    A[检查样式文件路径] --> B[验证 CSS 声明]
    B --> C[检查 inlineCss 配置]
    C --> D{问题是否解决}
    D -->|是| E[完成]
    D -->|否| F[检查 RSC 配置]
    F --> D

最佳实践

样式组织规范

类型存放位置引用方式
全局样式src/styles/入口文件 import
组件样式组件同目录 .css动态导入
Tailwindsrc/styles/app.cssPostCSS 处理
CSS Modules组件同名 .module.css组件内引用

性能优化建议

  1. 样式内联:对于关键 CSS,优先使用内联方式
  2. 懒加载:非首屏样式使用 data-rsc-css-href 懒加载
  3. 预加载:在 head 中声明 <link rel="preload"> 提前加载
  4. 缓存策略:配置合理的 Cache-Control 头

相关资源

包依赖关系

包名用途
@tanstack/start-plugin-coreCSS 内联和样式处理核心
@tanstack/react-start-rscRSC 模式下的 CSS 收集
@tanstack/react-routerHeadContent 和标签管理

资料来源:packages/start-plugin-core/src/start-manifest-plugin/inlineCss.ts:1-30

失败模式与踩坑日记

保留 Doramagic 在发现、验证和编译中沉淀的项目专属风险,不把社区讨论只当作装饰信息。

high 来源证据:Prerender never exits with custom alias injected by Vite plugin

可能阻塞安装或首次运行。

high 来源证据:Set multiple 'theme-color' in head

可能增加新用户试用和生产接入成本。

medium 社区讨论暴露的待验证问题:Need help getting a static public IP for Dhan API with Jio Fibre + TP-Link router

这类外部讨论可能代表真实用户在安装、配置、升级或生产使用时遇到阻力;发布前不能只依赖官方 README。

medium 能力判断依赖假设

假设不成立时,用户拿不到承诺的能力。

Pitfall Log / 踩坑日志

项目:tanstack/router

摘要:发现 9 个潜在踩坑项,其中 2 个为 high/blocking;最高优先级:安装坑 - 来源证据:Prerender never exits with custom alias injected by Vite plugin。

1. 安装坑 · 来源证据:Prerender never exits with custom alias injected by Vite plugin

  • 严重度:high
  • 证据强度:source_linked
  • 发现:GitHub 社区证据显示该项目存在一个安装相关的待验证问题:Prerender never exits with custom alias injected by Vite plugin
  • 对用户的影响:可能阻塞安装或首次运行。
  • 建议检查:来源问题仍为 open,Pack Agent 需要复核是否仍影响当前版本。
  • 防护动作:不得脱离来源链接放大为确定性结论;需要标注适用版本和复核状态。
  • 证据:community_evidence:github | cevd_3a21ac0bc00b4987aa25da92e3db84e0 | https://github.com/TanStack/router/issues/7516 | 来源类型 github_issue 暴露的待验证使用条件。

2. 维护坑 · 来源证据:Set multiple 'theme-color' in head

  • 严重度:high
  • 证据强度:source_linked
  • 发现:GitHub 社区证据显示该项目存在一个维护/版本相关的待验证问题:Set multiple 'theme-color' in head
  • 对用户的影响:可能增加新用户试用和生产接入成本。
  • 建议检查:来源问题仍为 open,Pack Agent 需要复核是否仍影响当前版本。
  • 防护动作:不得脱离来源链接放大为确定性结论;需要标注适用版本和复核状态。
  • 证据:community_evidence:github | cevd_cb6e2613932741d0bf8d64f4492a37f4 | https://github.com/TanStack/router/issues/5146 | 来源类型 github_issue 暴露的待验证使用条件。

3. 安装坑 · 社区讨论暴露的待验证问题:Need help getting a static public IP for Dhan API with Jio Fibre + TP-Link router

  • 严重度:medium
  • 证据强度:source_linked
  • 发现:Need help getting a static public IP for Dhan API with Jio Fibre + TP-Link router Hey everyone, I'm building an automated trading system using n8n that places orders through the Dhan broker API. Dhan requires a whitelisted static public IP to allow API calls, without it I get error DH-905 "Invalid IP". My setup: \- IS…
  • 对用户的影响:这类外部讨论可能代表真实用户在安装、配置、升级或生产使用时遇到阻力;发布前不能只依赖官方 README。
  • 建议检查:Pack Agent 需要打开来源链接,确认问题是否仍然存在,并把验证结论写入说明书和边界卡。
  • 证据:social_signal:reddit | ssig_3bb75104388c458abe8f8e44e685c9c5 | https://www.reddit.com/r/selfhosted/comments/1tka9n4/need_help_getting_a_static_public_ip_for_dhan_api/ | Need help getting a static public IP for Dhan API with Jio Fibre + TP-Link router

4. 能力坑 · 能力判断依赖假设

  • 严重度:medium
  • 证据强度:source_linked
  • 发现:README/documentation is current enough for a first validation pass.
  • 对用户的影响:假设不成立时,用户拿不到承诺的能力。
  • 建议检查:将假设转成下游验证清单。
  • 防护动作:假设必须转成验证项;没有验证结果前不能写成事实。
  • 证据:capability.assumptions | hn_item:48100706 | https://news.ycombinator.com/item?id=48100706 | README/documentation is current enough for a first validation pass.

5. 维护坑 · 维护活跃度未知

  • 严重度:medium
  • 证据强度:source_linked
  • 发现:未记录 last_activity_observed。
  • 对用户的影响:新项目、停更项目和活跃项目会被混在一起,推荐信任度下降。
  • 建议检查:补 GitHub 最近 commit、release、issue/PR 响应信号。
  • 防护动作:维护活跃度未知时,推荐强度不能标为高信任。
  • 证据:evidence.maintainer_signals | hn_item:48100706 | https://news.ycombinator.com/item?id=48100706 | last_activity_observed missing

6. 安全/权限坑 · 下游验证发现风险项

  • 严重度:medium
  • 证据强度:source_linked
  • 发现:no_demo
  • 对用户的影响:下游已经要求复核,不能在页面中弱化。
  • 建议检查:进入安全/权限治理复核队列。
  • 防护动作:下游风险存在时必须保持 review/recommendation 降级。
  • 证据:downstream_validation.risk_items | hn_item:48100706 | https://news.ycombinator.com/item?id=48100706 | no_demo; severity=medium

7. 安全/权限坑 · 存在评分风险

  • 严重度:medium
  • 证据强度:source_linked
  • 发现:no_demo
  • 对用户的影响:风险会影响是否适合普通用户安装。
  • 建议检查:把风险写入边界卡,并确认是否需要人工复核。
  • 防护动作:评分风险必须进入边界卡,不能只作为内部分数。
  • 证据:risks.scoring_risks | hn_item:48100706 | https://news.ycombinator.com/item?id=48100706 | no_demo; severity=medium

8. 维护坑 · issue/PR 响应质量未知

  • 严重度:low
  • 证据强度:source_linked
  • 发现:issue_or_pr_quality=unknown。
  • 对用户的影响:用户无法判断遇到问题后是否有人维护。
  • 建议检查:抽样最近 issue/PR,判断是否长期无人处理。
  • 防护动作:issue/PR 响应未知时,必须提示维护风险。
  • 证据:evidence.maintainer_signals | hn_item:48100706 | https://news.ycombinator.com/item?id=48100706 | issue_or_pr_quality=unknown

9. 维护坑 · 发布节奏不明确

  • 严重度:low
  • 证据强度:source_linked
  • 发现:release_recency=unknown。
  • 对用户的影响:安装命令和文档可能落后于代码,用户踩坑概率升高。
  • 建议检查:确认最近 release/tag 和 README 安装命令是否一致。
  • 防护动作:发布节奏未知或过期时,安装说明必须标注可能漂移。
  • 证据:evidence.maintainer_signals | hn_item:48100706 | https://news.ycombinator.com/item?id=48100706 | release_recency=unknown

来源:Doramagic 发现、验证与编译记录