TanStack Start 实战指南:Start + Router + Query + Store 全链路
TanStack 团队在 2024 年发布了 TanStack Start,一个结合 Router、Query、Virtual、Form、Store 等模块的 React 全栈脚手架。本文按照实际开发顺序,带你用 Start、Router、Query、Store 实现一个「知识库」小应用,涵盖工程化、数据获取、状态管理和生产部署要点。
1. TanStack 生态总览
| 模块 | 作用 | 在本示例中的职责 | | --- | --- | --- | | Start | 提供 Vite + React + TanStack Router 组合的 SSR/SSG 能力,支持服务器 Actions | 脚手架、构建与部署 | | Router | 文件式/声明式路由、数据加载器、错误边界 | 组织页面 Layout + 子页面导航 | | Query | 客户端/服务端共享的请求缓存层 | 拉取 GitHub Issues 作为知识库条目 | | Store | 极轻量的响应式状态容器 | 记录用户本地过滤条件和偏好 |
2. 使用 TanStack Start 初始化项目
-
安装脚手架
pnpm create tanstack@latest tanstart-demo cd tanstart-demo pnpm install选择
start模板、router与query插件,勾选typescript、tailwind可快速获得常用配置。 -
开发与构建命令
pnpm dev # 启动带 HMR 的本地服务器,默认 http://localhost:3000 pnpm lint # TanStack Start 默认集成 eslint + prettier pnpm build # 生成 SSR 包,可通过 pnpm preview / fly deploy 体验 pnpm test # 如选择 Vitest,会自动生成 vitest.config.ts -
项目结构速览
src/ routes/ __root.tsx # 根布局,放置 Layout、QueryClientProvider index.tsx # 首页 guides.tsx # 自定义子路由 server/ context.ts # 服务端 context(headers、db 实例) styles/ globals.cssStart 将 Router、Query、Store 挂接到
src/routes中的 React 组件里,SSR/SSG 自动处理。
3. 使用 TanStack Router 构建多层页面
-
定义根布局
src/routes/__root.tsximport { Outlet, Link } from '@tanstack/react-router' import { QueryClientProvider } from '@tanstack/react-query' import { queryClient } from '../services/query-client' export const Route = createRootRoute({ component: () => ( <QueryClientProvider client={queryClient}> <div className="min-h-screen bg-slate-950 text-slate-100"> <header className="border-b border-slate-800 px-6 py-4 flex gap-4"> <Link to="/" className="[&.active]:text-emerald-400">首页</Link> <Link to="/guides">知识库</Link> </header> <main className="px-6 py-8"> <Outlet /> </main> </div> </QueryClientProvider> ), }) -
配置子路由
- 首页
src/routes/index.tsx显示简介。 - 知识库
src/routes/guides.tsx依赖 loader(beforeLoad或loader)读取搜索参数,并使用 TanStack Store 存储过滤器。
export const Route = createFileRoute('/guides')({ component: GuidesPage, }) - 首页
Router 的优势在于:声明式 loader + suspense 支持,URL 与状态天然同步,不需要额外的 react-router 包装。
4. 集成 TanStack Query 拉取远程数据
-
配置 Query Client
src/services/query-client.tsimport { QueryClient } from '@tanstack/react-query' export const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 1000 * 60, retry: 1, gcTime: 1000 * 60 * 5, }, }, }) -
创建复用的查询描述
import { queryOptions } from '@tanstack/react-query' const repoOwner = 'tanstack' const repoName = 'router' export const guideListQuery = (label: string) => queryOptions({ queryKey: ['guides', label], queryFn: async () => { const res = await fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/issues?labels=${label}`) if (!res.ok) throw new Error('加载失败') return res.json() as Promise<Array<{ id: number; title: string; html_url: string }>> }, }) -
在路由组件中使用
import { useQuery } from '@tanstack/react-query' import { guideListQuery } from '../services/queries' import { useGuideStore } from '../stores/guide-store' function GuidesPage() { const { label } = useGuideStore() const { data, isLoading } = useQuery(guideListQuery(label)) if (isLoading) return <p>同步 GitHub 数据中...</p> return ( <ul className="space-y-3"> {data?.map((issue) => ( <li key={issue.id} className="rounded border border-slate-800 p-4"> <a href={issue.html_url} target="_blank" rel="noreferrer">{issue.title}</a> <p className="text-xs text-slate-400">Issue #{issue.id}</p> </li> ))} </ul> ) }
TanStack Query 的 queryOptions 能与 Router 的 loader 共享缓存:在服务器端调用 queryClient.ensureQueryData(guideListQuery(label)) 后,客户端便可直接复用数据避免二次请求。
5. 使用 TanStack Store 管理本地偏好
TanStack Store 是无依赖、响应式的原子状态容器,像一个轻量版的 signals。
-
创建 store
src/stores/guide-store.tsimport { Store } from '@tanstack/store' type GuideState = { label: string displayMode: 'card' | 'list' } export const guideStore = new Store<GuideState>({ label: 'good first issue', displayMode: 'list', }) export const useGuideStore = () => { const [state, setState] = React.useSyncExternalStore( (listener) => guideStore.subscribe(listener), () => guideStore.state, ) const updateLabel = (label: string) => guideStore.setState((prev) => ({ ...prev, label })) const toggleDisplay = () => guideStore.setState((prev) => ({ ...prev, displayMode: prev.displayMode === 'list' ? 'card' : 'list', })) return { ...state, updateLabel, toggleDisplay } } -
在组件中消费
function GuideToolbar() { const { label, updateLabel, displayMode, toggleDisplay } = useGuideStore() return ( <div className="flex items-center gap-3 mb-6"> <select value={label} onChange={(e) => updateLabel(e.target.value)}> <option value="good first issue">入门</option> <option value="help wanted">进阶</option> </select> <button onClick={toggleDisplay}>视图:{displayMode}</button> </div> ) }
Store 的 setState 可用于路由 loader 中(同步初始 URL 查询参数),也可在服务端渲染时根据 cookies 注入用户偏好。
6. 将 Router、Query、Store 串联成可复用模式
-
在 Loader 中预取数据
export const Route = createFileRoute('/guides')({ loader: async ({ context, params, search }) => { const label = search.label ?? guideStore.state.label guideStore.setState((prev) => ({ ...prev, label })) await context.queryClient.ensureQueryData(guideListQuery(label)) return { label } }, component: GuidesPage, })context.queryClient来自router的 server context,可在src/router.ts创建。- Loader 返回的
label可通过Route.useLoaderData()获取,用于在组件中初始化 store。
-
表单 + Action 示例
- 在
src/routes/contact.tsx中定义action调用 server 函数,并在客户端使用<Form />组件提交数据。 - 成功后用
router.navigate({ to: '/guides', search: { label: 'custom' } })跳转到新的 Query。
- 在
-
错误和边界处理
- Start 默认启用 Error Boundary:在
createFileRoute中传入errorComponent。 - 当 Query 请求失败时,可以抛出错误交由 Router 处理,统一渲染「重试」按钮。
- Start 默认启用 Error Boundary:在
7. 开发流程与部署建议
-
本地开发
- 运行
pnpm dev --host方便移动端调试。 - 利用
tanstack.config.ts定制 routes 生成逻辑(如 alias 或多语言)。
- 运行
-
测试与质量保障
- 通过
pnpm test调起 Vitest,对 store 与 hooks 编写单元测试。 - 使用 Playwright 检查 Router 的关键跳转和 Query 的 Loading 状态。
- 通过
-
部署
- 默认输出为 Node SSR;可部署到 Vercel、Fly.io 或自建 Node 服务器。
- 若需要边缘部署,可开启基于
nitro的适配器或使用 Cloudflare Workers(TanStack Start 提供 template)。
-
配置建议
- 在
.env中放置 GitHub Token,使用context.env在 loader 中访问,提升 API 速率。 - 借助
@tanstack/router-devtools与@tanstack/react-query-devtools观察 Query 状态,开发阶段以懒加载方式引入。
- 在
8. 深入探索的路线图
- 集成 TanStack Form:可为 Contact 表单添加 Zod 校验。
- 使用 File-based RPC:Start 可以把
src/server里的函数直接暴露给客户端,利用 Router 的action机制调用。 - 联动其他后端:通过
context注入 Prisma/Drizzle,利用 Query + Router loader 构造全栈 CRUD。
完成以上步骤后,你已经拥有了一个具备路由、数据获取、状态管理、服务器行为的 TanStack Start 项目模板,可以直接扩展为知识库、仪表盘或 SaaS Admin。动手实践并根据团队规范迭代,即可在实际业务中享受 TanStack 生态的高可组合性。