mcpField
T
tanstack-start

tanstack-start

github.com/mindrally开发28869 次安装3 周前更新
代码仓库

关于 tanstack-start

TanStack Start 是面向 Web 开发者的全栈 React 框架,以单一类型安全代码库整合服务端函数、SSR、流式传输和 API 路由。

主要功能

  1. 01类型安全的服务端函数
  2. 02服务端渲染
  3. 03延迟数据流式传输
  4. 04基于文件的路由
  5. 05原始 HTTP API 路由

适用场景

  1. 01构建全栈 React 应用
  2. 02以流式方式提供页面数据
  3. 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 功能的工作流

  1. 配置应用 — 设置 app.config.ts,配置 Vite 插件,并设置与部署目标相匹配的 server.preset。
  2. 定义根路由 — 创建 src/routes/__root.tsx,其中包含 HTML 文档框架(<html>、<head>、<body>、<Outlet>、<ScrollRestoration>、<Scripts>)。
  3. 编写服务端函数 — 使用 createServerFn(),通过 .validator() 和 .handler() 实现任何必须在服务端运行的逻辑(数据库访问、机密信息、文件 I/O)。
  4. 将服务端函数接入路由 — 在路由的 loader 中调用服务端函数来读取数据,并在变更钩子(useMutation)中调用以写入数据。
  5. 为原始 HTTP 请求添加 API 路由 — 使用 createAPIFileRoute 处理 Webhook、OAuth 回调,或外部服务直接调用的端点。
  6. 流式传输非关键数据 — 将缓慢且非阻塞的 loader 数据包装在 defer() 中,并使用 <Suspense> + <Await> 渲染。
  7. 部署 — 为目标平台选择 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