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 作为包管理器
安装步骤
根据你的框架选择对应的包进行安装:
| 框架 | 安装命令 |
|---|---|
| React | pnpm add @tanstack/react-router |
| Solid | pnpm 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>
}
数据加载器
使用 beforeLoad 或 loader 进行数据预取:
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 验证 |
| 热更新 | 开发环境实时重新生成 |
开发者只需遵循命名约定组织文件结构,即可获得生产级的路由管理能力。快速启动模板
快速启动模板是 TanStack Router 为开发者提供的开箱即用项目模板,旨在帮助开发者快速搭建基于 TanStack Router 的应用。这些模板覆盖了主流的构建工具,包括 Vite、Webpack 和 Rspack,支持文件路由和代码分割等核心功能。模板设计遵循最佳实践,提供了完整的项目结构、路由配置示例和开发服务器集成,使开发者能够在几分钟内启动一个新项目。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
快速启动模板是 TanStack Router 为开发者提供的开箱即用项目模板,旨在帮助开发者快速搭建基于 TanStack Router 的应用。这些模板覆盖了主流的构建工具,包括 Vite、Webpack 和 Rspack,支持文件路由和代码分割等核心功能。模板设计遵循最佳实践,提供了完整的项目结构、路由配置示例和开发服务器集成,使开发者能够在几分钟内启动一个新项目。
TanStack Router 是一个类型安全的现代路由库,专为 React 和 Solid 等框架设计。它提供了声明式的路由定义、嵌套路由、路由匹配、加载状态管理和搜索参数处理等功能。通过快速启动模板,开发者可以避免繁琐的初始配置,直接专注于业务逻辑开发。
模板类型
TanStack Router 提供了三种官方快速启动模板,分别针对不同的构建工具进行优化。每种模板都包含相同的核心功能,只是构建工具的配置方式有所不同。
| 模板名称 | 构建工具 | 特点 | 适用场景 |
|---|---|---|---|
| quickstart-vite-file-based | Vite | 原生 ESM 支持,快速热更新 | 首选推荐,现代前端开发 |
| quickstart-webpack-file-based | Webpack | 成熟稳定,广泛兼容 | 遗留项目,企业环境 |
| quickstart-rspack-file-based | Rspack | Rust 实现,更快的构建速度 | 大型项目,性能敏感场景 |
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 提供了强大的数据加载能力,在路由配置中使用 beforeLoad 和 loader 钩子:
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 的最佳实践,包括文件路由约定、类型安全路由定义和代码分割策略。同时,开发者应关注社区中报告的性能和辅助功能问题,以便在项目中及时采取相应的优化措施。
核心包架构
TanStack Router 是一个类型安全的路由库,采用模块化架构设计,核心功能与框架特定实现分离。本页面详细说明其核心包架构、各组件职责及相互关系。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
架构概览
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[渲染组件]关键函数
| 函数 | 文件位置 | 职责 |
|---|---|---|
processRouteTree | new-process-route-tree.ts | 处理完整路由树 |
findFlatMatch | new-process-route-tree.ts | 查找扁平化匹配 |
buildRouteBranch | new-process-route-tree.ts | 构建路由分支 |
processRouteMasks | new-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 的核心包架构体现了以下设计原则:
- 关注点分离 - 核心逻辑与框架实现分离
- 类型安全 - 代码生成提供完整类型推断
- 性能优先 - LRU 缓存、深度比较优化
- 响应式设计 - Store 模式管理状态
- 模块化 - 清晰的依赖层次结构
这套架构使得 TanStack Router 能够同时支持多个前端框架,同时保持一致的功能和 API 体验。
核心概念与类型系统
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 时,路由器需要:
- 路径解析:将 URL 转换为内部路径表示
- 分支匹配:在路由树中查找匹配的路由分支
- 参数提取:从 URL 中提取路径参数和查询参数
- 类型推断:基于匹配的路由推断正确的类型
匹配过程涉及三个核心函数:
| 函数 | 职责 | 资料来源 |
|---|---|---|
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核心存储列表:
| 存储名称 | 类型 | 用途 |
|---|---|---|
matches | Store<RouteMatch[]> | 当前匹配的路由列表 |
params | Store<Record<string, string>> | 路径参数 |
search | Store<Record<string, unknown>> | 查询参数 |
location | Store<Location> | 完整位置信息 |
状态更新流程
路由器状态更新遵循以下流程:
- 历史变化检测:监听
popstate、pushState、replaceState事件 - 路由匹配计算:调用
findRouteMatch计算新路径的匹配结果 - 加载器执行:按需执行路由的
loader函数 - 状态同步:更新内部存储,触发响应式更新
- 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 重写与重定向配置 |
数据加载与状态管理
TanStack Router 提供了一套完整的数据加载与状态管理体系,旨在为现代 Web 应用提供类型安全的数据获取、状态管理和路由同步能力。该系统核心设计理念包括:
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
TanStack Router 提供了一套完整的数据加载与状态管理体系,旨在为现代 Web 应用提供类型安全的数据获取、状态管理和路由同步能力。该系统核心设计理念包括:
- 端到端类型安全:从路由参数到 loader 数据,整个数据流都具备完整的 TypeScript 类型推导
- 声明式数据加载:通过 route loader 模式,将数据获取逻辑与路由定义紧密绑定
- 统一的状态管理:利用响应式 store 机制,实现路由状态与应用状态的无缝集成
- 灵活的数据转换:支持自定义搜索参数的解析和序列化逻辑
该系统主要涉及以下核心模块:
| 模块 | 职责 | 核心文件 |
|---|---|---|
| Loader 系统 | 路由级数据获取与缓存 | load-matches.ts |
| 搜索参数处理 | URL 查询字符串的类型化解析与序列化 | searchParams.ts |
| 状态存储 | 响应式状态管理 | stores.ts |
| 路径与 URL 处理 | 路径插值与 URL 构建 | path.ts、qss.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 指出路由变化时缺乏适当的焦点管理和语义化宣告,影响屏幕阅读器用户的使用体验。
最佳实践
数据加载模式
- 集中式数据获取:在路由级别定义 loader,避免在组件中直接请求数据
- 类型安全的参数:使用 schema 验证所有外部输入(params、search)
- 合理的缓存策略:根据数据更新频率配置适当的缓存时长
- 错误边界设计:为每个路由配置适当的错误处理组件
状态管理建议
| 场景 | 推荐方案 |
|---|---|
| 路由级数据 | 使用 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
})
相关配置选项
| 选项 | 类型 | 默认值 | 说明 | ||
|---|---|---|---|---|---|
loaderGcTime | number | 300000 | Loader 数据缓存时间(毫秒) | ||
staleTime | number | 0 | 数据被认为过期的时长 | ||
preloadOnHover | boolean | true | 悬停时是否预加载 | ||
defaultPreload | `'intent' \ | 'render' \ | false` | 'intent' | 默认预加载策略 |
defaultPreloadDelay | number | 50 | 预加载延迟(毫秒) |
来源:https://github.com/TanStack/router / 项目说明书
Search Params 验证与序列化
Search Params(搜索参数)验证与序列化是 TanStack Router 中处理 URL 查询字符串的核心功能模块。该模块负责在路由导航时将 TypeScript 对象与 URL 查询字符串之间进行双向转换,并提供中间件机制来控制搜索参数的传递行为。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
核心概念
搜索参数类型定义
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()
}
关键特性:
- 使用
URLSearchParamsAPI 确保浏览器兼容性 - 单值编码(每个 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),
})
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
验证适配器对比
| 特性 | Zod | Valibot | Arktype |
|---|---|---|---|
| 包大小 | ~38kB | ~14kB | ~15kB |
| Tree-shaking | 部分支持 | 完全支持 | 完全支持 |
| TypeScript 类型推断 | 强 | 强 | 最强 |
| 默认值支持 | 原生支持 | 需要使用 optional() | 原生支持 |
| 类型转换 | z.coerce | pipe() | @ 操作符 |
实际使用示例
基础搜索路由
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,
},
})
}
}
社区相关问题
根据社区反馈,搜索参数功能相关的常见问题包括:
- CSS Modules 问题 (#3023) - 部分用户在使用 TanStack Start 时遇到样式加载问题,可能与 SSR 时的搜索参数处理相关
- 认证模式性能问题 (#3997) - 使用推荐的身份验证模式时,
beforeLoad钩子在每个页面导航时都会运行,可能影响包含复杂搜索参数验证的路由性能
- 链接点击后内容未加载 (#597) - 某些情况下链接路径改变但页面内容未加载,可能与搜索参数解析错误相关
最佳实践
- 使用类型安全的 Schema 验证
- 推荐使用 Zod、Valibot 或 Arktype 定义搜索参数 Schema
- 利用 TypeScript 推断获得完整的类型提示
- 合理使用中间件
- 使用
stripSearchParams清理 URL,保持简洁 - 使用
retainSearchParams保持重要的上下文参数
- 设置合理的默认值
- 在 Schema 中使用
.default()设置默认值 - 避免 URL 中出现冗余参数
- 性能考虑
- 对于高频导航的路由,避免使用过于复杂的验证逻辑
- 考虑使用
retainSearchParams减少不必要的参数传递
相关文档
资料来源:router.ts:15-28
认证模式与性能优化
TanStack Router 提供了灵活且类型安全的认证路由模式,允许开发者实现基于路由的访问控制。该框架通过路由层级结构、beforeLoad 生命周期钩子以及 Router Context 来管理认证状态和权限验证。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
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 Context | router.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 关键参数
| 参数 | 类型 | 默认值 | 说明 | ||
|---|---|---|---|---|---|
history | RouterHistory | BrowserHistory | 历史记录管理 | ||
stringifySearch | SearchSerializer | defaultStringifySearch | 搜索参数序列化 | ||
parseSearch | SearchParser | defaultParseSearch | 搜索参数解析 | ||
defaultPreload | `false \ | 'intent' \ | 'viewport'` | false | 预加载策略 |
总结
TanStack Router 的认证模式通过布局路由和 beforeLoad 钩子提供了强大且类型安全的访问控制能力。然而,在大规模应用中需要特别注意性能优化,包括合理利用 Context 缓存、优化路由结构以及避免不必要的重复检查。官方文档建议开发者根据具体使用场景选择合适的预加载策略,以在安全性和性能之间取得平衡。
资料来源:examples/react/authenticated-routes/src/routes/_auth.tsx:1-17
SSR 服务端渲染支持
TanStack Router 提供完整的服务端渲染(SSR)支持,允许在服务器端预渲染路由并向客户端传递脱水状态,实现无缝的水合(Hydration)过程。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
核心架构
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 请求对象,返回一个处理函数。
关键实现流程
服务端处理流程包含以下步骤:
- 创建路由实例:调用
createRouter()初始化路由器 - 注入 SSR 工具:通过
attachRouterServerSsrUtils附加服务端工具集 - URL 规范化:使用
getNormalizedURL规范化请求路径 - 内存历史创建:使用
createMemoryHistory创建服务端历史记录 - 路由匹配与渲染:执行服务端路由匹配和组件渲染
资料来源: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 中序列化的状态结构:
| 属性 | 缩写 | 类型 | 说明 |
|---|---|---|---|
i | id | string | 路由匹配 ID |
b | beforeLoadContext | object | beforeLoad 上下文 |
l | loaderData | unknown | 加载器数据 |
s | status | number | HTTP 状态码 |
ssr | ssr | boolean | 是否为 SSR 渲染 |
u | updatedAt | number | 更新时间戳 |
e | error | Error | 错误对象 |
水合流程
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 内容。
导出清单
服务端导出
| 导出项 | 来源 | 说明 |
|---|---|---|
createRequestHandler | router-core | 创建 SSR 请求处理器 |
attachRouterServerSsrUtils | router-core | 附加 SSR 工具集 |
getNormalizedURL | router-core | URL 规范化 |
getOrigin | router-core | 获取请求来源 |
客户端导出
| 导出项 | 来源 | 说明 |
|---|---|---|
hydrate | router-core/ssr | 客户端水合函数 |
mergeHeaders | router-core/ssr | 头部合并工具 |
json | router-core/ssr | JSON 响应助手 |
TsrSsrGlobal | router-core/ssr | 全局类型 |
DehydratedMatch | router-core/ssr | 脱水匹配类型 |
DehydratedRouter | router-core/ssr | 脱水路由类型 |
常见问题
样式闪烁(FOUC)
社区反馈 CSS Modules 在 TanStack Start 中仅在客户端加载,导致初始渲染闪烁。问题源于样式未在服务端预加载。
解决方向:确保样式资源在 SSR 阶段一并序列化到 HTML 中。
认证模式性能
使用 onBeforeLoad 进行认证检查时,每个页面导航都会触发,导致性能问题。建议将认证检查提升到更粗粒度的层级。
辅助功能
当前路由切换时缺少屏幕阅读器公告和焦点管理,这是 v1 承诺但尚未完全实现的功能。
相关资源
- TanStack Start 文档
- SSR 指南
- @tanstack/solid-router-ssr-query - Solid SSR 查询集成
- React Start RSC 示例:
examples/react/start-rscs
资料来源:packages/router-core/src/ssr/createRequestHandler.ts:1-50
CSS 模块与样式方案
TanStack Start 提供了灵活的 CSS 和样式方案支持,旨在解决服务端渲染(SSR)应用中的样式加载问题,包括样式模块化、CSS 内联、样式预加载等核心功能。该方案确保在服务端和客户端渲染时样式的一致性,避免无样式内容闪烁(FOUC)问题。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
继续阅读本节完整说明和来源证据。
概述
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)
问题描述:样式仅在客户端加载,初始渲染时出现无样式内容闪烁。
解决方案:
- 确保 CSS 文件在服务端预加载
- 使用 RSC 模式的内联 CSS 机制
- 在路由配置中正确声明 CSS 依赖
问题二:样式不匹配
问题描述:服务端和客户端渲染的样式不一致。
排查步骤:
graph TD
A[检查样式文件路径] --> B[验证 CSS 声明]
B --> C[检查 inlineCss 配置]
C --> D{问题是否解决}
D -->|是| E[完成]
D -->|否| F[检查 RSC 配置]
F --> D最佳实践
样式组织规范
| 类型 | 存放位置 | 引用方式 |
|---|---|---|
| 全局样式 | src/styles/ | 入口文件 import |
| 组件样式 | 组件同目录 .css | 动态导入 |
| Tailwind | src/styles/app.css | PostCSS 处理 |
| CSS Modules | 组件同名 .module.css | 组件内引用 |
性能优化建议
- 样式内联:对于关键 CSS,优先使用内联方式
- 懒加载:非首屏样式使用
data-rsc-css-href懒加载 - 预加载:在 head 中声明
<link rel="preload">提前加载 - 缓存策略:配置合理的 Cache-Control 头
相关资源
包依赖关系
| 包名 | 用途 |
|---|---|
@tanstack/start-plugin-core | CSS 内联和样式处理核心 |
@tanstack/react-start-rsc | RSC 模式下的 CSS 收集 |
@tanstack/react-router | HeadContent 和标签管理 |
资料来源:packages/start-plugin-core/src/start-manifest-plugin/inlineCss.ts:1-30
失败模式与踩坑日记
保留 Doramagic 在发现、验证和编译中沉淀的项目专属风险,不把社区讨论只当作装饰信息。
可能阻塞安装或首次运行。
可能增加新用户试用和生产接入成本。
这类外部讨论可能代表真实用户在安装、配置、升级或生产使用时遇到阻力;发布前不能只依赖官方 README。
假设不成立时,用户拿不到承诺的能力。
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 发现、验证与编译记录