关于 tanstack-start
TanStack Start 是面向 Web 开发者的全栈 React 框架,以单一类型安全代码库整合服务端函数、SSR、流式传输和 API 路由。
主要功能
- 01类型安全的服务端函数
- 02服务端渲染
- 03延迟数据流式传输
- 04基于文件的路由
- 05原始 HTTP API 路由
适用场景
- 01构建全栈 React 应用
- 02以流式方式提供页面数据
- 03实现 Webhook 和 OAuth 回调
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ mindrally/ skills --skill tanstack-start安装后在 Claude Code 中输入/tanstack-start确认已加载。
详细介绍
TanStack Start
TanStack Start 是一个全栈 React 框架,在 TanStack Router 和 Vite/Nitro 构建流程之上整合了服务端函数、SSR、流式传输和 API 路由,可实现从服务端到客户端的端到端类型安全。
构建 TanStack Start 功能的工作流
- 配置应用 — 设置
app.config.ts,配置 Vite 插件,并设置与部署目标相匹配的server.preset。 - 定义根路由 — 创建
src/routes/__root.tsx,其中包含 HTML 文档框架(<html>、<head>、<body>、<Outlet>、<ScrollRestoration>、<Scripts>)。 - 编写服务端函数 — 使用
createServerFn(),通过.validator()和.handler()实现任何必须在服务端运行的逻辑(数据库访问、机密信息、文件 I/O)。 - 将服务端函数接入路由 — 在路由的
loader中调用服务端函数来读取数据,并在变更钩子(useMutation)中调用以写入数据。 - 为原始 HTTP 请求添加 API 路由 — 使用
createAPIFileRoute处理 Webhook、OAuth 回调,或外部服务直接调用的端点。 - 流式传输非关键数据 — 将缓慢且非阻塞的 loader 数据包装在
defer()中,并使用<Suspense>+<Await>渲染。 - 部署 — 为目标平台选择
server.preset,并在发布前检查环境变量的访问规则。
核心原则
- TanStack Start = TanStack Router + Vite + Nitro,可通过单一类型安全代码库实现全栈 React。
createServerFn是服务端逻辑的主要实现机制 — 内部数据访问优先使用它,而不是手动编写 REST 端点。- 所有 TanStack Router 约定都适用于 Start:基于文件的路由、loader、搜索参数验证和路由器上下文。
- 流式传输和 Suspense 是一等功能;对于任何不应阻塞初始响应的内容,都应使用
defer()。 - 严格将仅服务端代码(机密信息、数据库客户端、文件系统访问)放在服务端函数或 API 路由中 — 切勿将其导入客户端组件。
配置
app.config.ts
import { defineConfig } from '@tanstack/start/config'
import tsConfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
vite: { plugins: [tsConfigPaths()] },
server: {
preset: 'node-server', // or: 'vercel', 'netlify', 'bun', 'cloudflare-pages'
},
})
根路由 HTML 框架
// src/routes/__root.tsx
import { createRootRoute, Outlet, ScrollRestoration } from '@tanstack/react-router'
import { Scripts } from '@tanstack/start'
export const Route = createRootRoute({
component: () => (
<html lang="en">
<head />
<body>
<Outlet />
<ScrollRestoration />
<Scripts />
</body>
</html>
),
})
服务端函数
- 将服务端函数定义在专用的
src/server/functions/模块(或同目录的*.server.ts文件)中,便于查找。 - 始终使用
.validator()验证输入(常用选择是 Zod),然后再运行处理程序 — 切勿信任客户端提供的data。 - 在处理程序中抛出描述清晰的
Error(或类型化错误);TanStack Start 会将抛出的错误序列化并返回客户端。 - 服务端函数可直接从 loader、action、事件处理程序或
useMutation调用 — 它们在客户端上的行为类似普通异步函数,但实际在服务端执行。
// src/server/functions/posts.ts
import { createServerFn } from '@tanstack/start'
import { z } from 'zod'
import { db } from '~/server/db'
export const getPost = createServerFn()
.validator(z.object({ id: z.string() }))
.handler(async ({ data }) => {
const post = await db.post.findUnique({ where: { id: data.id } })
if (!post) throw new Error('Post not found')
return post
})
export const createPost = createServerFn()
.validator(z.object({ title: z.string().min(1), body: z.string() }))
.handler(async ({ data }) => db.post.create({ data }))
在路由中使用服务端函数
export const Route = createFileRoute('/posts/$postId')({
loader: ({ params }) => getPost({ data: { id: params.postId } }),
component: PostDetail,
})
function PostDetail() {
const post = Route.useLoaderData()
return <h1>{post.title}</h1>
}
使用服务端函数执行变更
import { useMutation } from '@tanstack/react-query'
import { useQueryClient } from '@tanstack/react-query'
import { createPost } from '~/server/functions/posts'
function NewPostForm() {
const queryClient = useQueryClient()
const mutation = useMutation({
mutationFn: (input: { title: string; body: string }) => createPost({ data: input }),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['posts'] }),
})
return (
<form
onSubmit={(e) => {
e.preventDefault()
const form = new FormData(e.currentTarget)
mutation.mutate({
title: String(form.get('title')),
body: String(form.get('body')),
})
}}
>
<input name="title" placeholder="Title" required />
<textarea name="body" placeholder="Body" required />
<button type="submit" disabled={mutation.isPending}>Publish</button>
</form>
)
}
API 路由
对于必须使用原始 HTTP 的端点(Webhook、OAuth 回调、运行状况检查),请使用 API 路由,而不是服务端函数;服务端函数旨在供你自己的客户端代码调用。
// src/routes/api/webhook.ts
import { createAPIFileRoute } from '@tanstack/start/api'
export const Route = createAPIFileRoute('/api/webhook')({
POST: async ({ request }) => {
const body = await request.json()
// verify signature, enqueue processing, etc.
return Response.json({ received: true })
},
})
使用 defer() 进行流式传输
- 仅等待初始渲染所需的数据;将其他所有内容包装在
defer()中,以便立即渲染页面框架。 - 将延迟值与
<Suspense>和<Await>配合使用,使界面在 Promise 完成前显示回退内容。
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
const post = await getPost({ data: { id: params.postId } }) // critical, awaited
const comments = getComments({ data: { postId: params.postId } }) // not awaited
return { post, comments: defer(comments) }
},
component: PostDetail,
})
function PostDetail() {
const { post, comments } = Route.useLoaderData()
return (
<div>
<h1>{post.title}</h1>
<Suspense fallback={<CommentsSkeleton />}>
<Await promise={comments}>{(c) => <CommentsList comments={c} />}</Await>
</Suspense>
</div>
)
}
环境变量
- 仅在服务端函数或 API 路由中通过
process.env访问仅服务端使用的机密信息。 - 对于必须暴露给客户端资源包的变量,请使用
import.meta.env.VITE_*。 - 切勿在客户端组件中直接引用
process.env— Vite 不会内联服务端机密信息,这样做也很容易导致配置泄露。
部署目标
在 app.config.ts 中配置 server.preset,使其与托管平台相匹配:
node-server— 默认选项,可在任何能运行 Node.js 的环境中运行。vercel— Vercel 无服务器/边缘函数。netlify— Netlify Functions。bun— Bun 运行时。cloudflare-pages— Cloudflare Pages + Workers(请留意服务端函数中 Node API 的兼容性缺口)。
常见错误
- 在客户端组件中直接调用服务端函数的处理逻辑,而不是通过
createServerFn调用——这会破坏客户端与服务端的边界,并影响类型推断。 - 在加载器中等待所有内容完成,这会违背流式传输的初衷,并拖慢初始响应。
- 忘记
.validator(),导致处理程序信任未经验证的客户端输入。 - 在客户端和服务端代码都会导入的共享工具模块中读取
process.env。
评论
支持 Markdown