首页 / TanStack 生态入门教程 / Start 全栈框架概览

TanStack 生态入门教程

Start 全栈框架概览

本教程共 38 篇 · 第 22 篇 · 更新于 2026-07-27 · 约 14 分钟阅读

TanStackTanStack 生态入门教程TanStack Start全栈框架ViteSSRServer FunctionsNext.js 对比

22. Start 全栈框架概览

本节目标:搞懂 TanStack Start 是什么、为什么需要它、跟 Next.js 有什么区别。学会用 CLI 或手动方式创建一个 Start 项目,理解项目目录结构。学完你能判断 Start 是否适合你的项目,并跑起第一个应用。

22.1 TanStack Start 是什么

前面学了 TanStack Router 的路由、loader、搜索参数、类型安全导航。Router 是一个纯客户端路由库,能做 SPA(单页应用),但做不了:

  • 服务端渲染(SSR)
  • 服务端 API 端点
  • 客户端调用服务端函数(RPC)
  • 全栈构建和部署

TanStack Start 就是补上这些能力的全栈框架。它基于 TanStack Router 构建,在 Router 的类型安全路由之上,加了 SSR、Server Functions、全栈构建等功能。

一句话定义:Start = Router + Vite + SSR + Server Functions

两大依赖

Start 建立在两个核心技术上:

  • TanStack Router:类型安全路由,Start 100% 依赖它做路由系统
  • Vite 或 Rsbuild:现代构建工具,提供快速开发和优化构建
Note

Start 当前处于 v1 Release Candidate(候选发布)阶段,API 已稳定但仍在向 1.0 稳定版收尾。版本线与 Router 同步,本教程基于 v1 RC 编写。注意 Start 迭代很快,旧方案(app.config.ts、vinxi)已过时,现以 Vite 插件方案为准。

什么时候用 Start,什么时候只用 Router

Router 已经能做很强大的 SPA。如果你确定不需要以下功能,只用 Router 就够了:

  • 服务端渲染(SEO 需求)
  • 服务端 API 端点
  • 客户端到服务端的类型安全调用
  • 全栈构建和部署

一旦需要其中任何一个,就该上 Start 了。

22.2 Start vs Next.js:两种思路

很多人会拿 Start 跟 Next.js 比。两者都是全栈 React 框架,但设计理念不同。

Next.js:平台优先

Next.js 优化的是平台集成:服务端优先渲染、与 Vercel 平台紧耦合、框架帮你做决策。

默认组件是 Server Component,不能用状态和事件处理器。要交互就加 "use client"。整个心智模型围绕服务端组件展开。

Start:开发者优先

Start 优化的是开发者控制力:类型安全无处不在、显式优于隐式、组合式原语、部署自由。

默认组件是交互式组件(传统 React),开箱就能用状态和事件处理器。Server Components 是可选的,哪里需要哪里开。

核心差异对比

方面TanStack StartNext.js
组件默认交互式(传统 React)Server Component
类型安全端到端,编译时检查有支持,但客户端/服务端边界有断层
构建工具Vite 或 RsbuildTurbopack/Webpack
缓存显式 SWR 模式(Router/Query)多层隐式缓存
部署各平台平等支持针对 Vercel 优化
路由TanStack Router(最强类型安全)文件式路由,基础类型
Server 函数类型安全 + 输入校验 + 中间件Server Actions,边界无类型
Tip

简单记:Next.js 像”全自动挡”,框架帮你做很多决策;Start 像”手动挡”,给你更多控制权。哪个好取决于你的项目和个人偏好。

缓存的区别

Next.js 的缓存是多层的:请求记忆、数据缓存、路由缓存、Router 缓存。每层有自己的失效规则,历史上改过好几次,社区反馈”难以预测”。

Start 用的是你已经熟悉的模式:

  • Router 内置 SWR 缓存staleTimegcTime 控制
  • TanStack Query:完整的数据状态管理
  • CDN 缓存:标准 HTTP 缓存头
  • Redis/数据库:想缓存就缓存,用你已有的基础设施

没有新的心智模型,用你已知的方式缓存数据。

22.3 创建 Start 项目

方式一:CLI 脚手架(推荐)

npx @tanstack/cli@latest create

按提示选择包管理器和可选插件(Tailwind CSS、ESLint 等),自动生成项目。

方式二:从示例克隆

# 克隆基础示例
npx gitpick TanStack/router/tree/main/examples/react/start-basic start-basic
cd start-basic
npm install
npm run dev

官方提供了不少示例:

  • start-basic - 基础示例
  • start-basic-auth - 带认证
  • start-basic-react-query - 集成 React Query
  • start-counter - 计数器(演示 Server Functions)
  • start-supabase-basic - 集成 Supabase
  • start-clerk-basic - 集成 Clerk 认证

方式三:从零搭建

想理解每个文件的作用,可以手动搭建。

22.4 从零搭建一个 Start 项目

初始化项目

mkdir my-start-app
cd my-start-app
npm init -y

安装依赖

# Start 和 Router
npm i @tanstack/react-start @tanstack/react-router

# React
npm i react react-dom

# 构建工具(Vite)
npm i -D vite @vitejs/plugin-react

# TypeScript
npm i -D typescript @types/react @types/react-dom @types/node

配置 package.json

{
  "type": "module",
  "scripts": {
    "dev": "vite dev",
    "build": "vite build"
  }
}

配置 Vite

// vite.config.ts
import { defineConfig } from 'vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import viteReact from '@vitejs/plugin-react'

export default defineConfig({
  server: {
    port: 3000,
  },
  plugins: [
    tanstackStart(),
    // React 插件必须在 Start 插件之后
    viteReact(),
  ],
})
Warning

viteReact() 必须放在 tanstackStart() 之后。顺序反了会出问题,我踩过这个坑。

配置 TypeScript

// tsconfig.json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "moduleResolution": "Bundler",
    "module": "ESNext",
    "target": "ES2022",
    "skipLibCheck": true,
    "strictNullChecks": true
  }
}
Note

不要开 verbatimModuleSyntax,否则服务端代码可能泄漏到客户端 bundle。

创建路由配置

// src/router.tsx
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'

export function getRouter() {
  const router = createRouter({
    routeTree,
    scrollRestoration: true,
  })

  return router
}
Tip

必须导出 getRouter 函数(不是直接导出 router 实例)。因为 SSR 环境下每个请求需要独立的 router 实例,函数确保每次调用都创建新的。

创建根路由

// src/routes/__root.tsx
import type { ReactNode } from 'react'
import {
  Outlet,
  createRootRoute,
  HeadContent,
  Scripts,
} from '@tanstack/react-router'

export const Route = createRootRoute({
  head: () => ({
    meta: [
      { charSet: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { title: '我的 Start 应用' },
    ],
  }),
  component: RootComponent,
})

function RootComponent() {
  return (
    <RootDocument>
      <Outlet />
    </RootDocument>
  )
}

function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
  return (
    <html>
      <head>
        <HeadContent />
      </head>
      <body>
        {children}
        <Scripts />
      </body>
    </html>
  )
}

根路由跟纯 Router 的根路由不一样,它要渲染完整的 HTML 文档(<html><head><body>),因为 Start 做 SSR,服务端要输出完整页面。

三个关键组件:

  • <HeadContent />:渲染 <head> 里的 meta、title、link 标签
  • <Outlet />:渲染匹配的子路由
  • <Scripts />:加载客户端 JavaScript(hydration 必须)

创建首页路由

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

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

function HomePage() {
  return (
    <div>
      <h1>Hello, TanStack Start!</h1>
      <p>你的第一个 Start 应用跑起来了。</p>
    </div>
  )
}

启动开发服务器

npm run dev

打开 http://localhost:3000,你会看到页面。第一次启动会自动生成 routeTree.gen.ts 文件。

22.5 项目目录结构

一个典型的 Start 项目长这样:

my-start-app/
├── src/
│   ├── routes/           # 路由文件(文件式路由)
│   │   ├── __root.tsx    # 根路由(HTML 文档外壳)
│   │   ├── index.tsx     # 首页 /
│   │   ├── about.tsx     # 关于页 /about
│   │   ├── posts.tsx     # 文章布局 /posts
│   │   └── posts.$postId.tsx  # 文章详情 /posts/$postId
│   ├── router.tsx        # 路由器配置
│   └── routeTree.gen.ts  # 自动生成的路由树(别手动改)
├── vite.config.ts        # Vite 配置
├── package.json
└── tsconfig.json

跟纯 Router 项目的区别

如果你之前用过纯 Router 做 SPA,Start 项目多了这些东西:

  • vite.config.ts 里的 tanstackStart() 插件:处理 SSR 构建、Server Functions 编译
  • __root.tsx 渲染完整 HTML 文档:纯 Router 只渲染 <div> 片段
  • getRouter() 函数:而不是直接导出 router 实例
  • <Scripts /> 组件:SSR hydration 必须

路由文件命名

跟纯 Router 完全一样,文件式路由的命名规则:

路径文件名类型
/index.tsx首页
/aboutabout.tsx静态路由
/posts/posts/index.tsx文章首页
/posts/$postIdposts.$postId.tsx动态路由
/rest/*rest/$.tsx通配路由
Note

createFileRoute 的路径参数是自动生成和维护的。你创建、移动、重命名路由文件时,路径会自动更新,不用手动改。

22.6 Start 的核心能力一览

后面几章会逐一深入,这里先有个全貌:

SSR(服务端渲染)

服务端渲染完整 HTML,用户首屏不用等 JavaScript 加载完才能看到内容。对 SEO 和首屏性能都重要。

Server Functions(服务器函数)

在服务端定义函数,客户端直接调用,类型安全。相当于内置的 RPC。

import { createServerFn } from '@tanstack/react-start'

// 服务端函数
const getCount = createServerFn({ method: 'GET' }).handler(() => {
  return 42
})

// 客户端直接调用
const count = await getCount()

Server Routes(API 路由)

在路由文件里定义后端 API 端点,客户端可以 fetch。

Middleware(中间件)

请求级别的中间件,处理认证、日志、CORS 等。能在 Server Functions 和 API 路由上用。

Streaming(流式传输)

服务端渲染时,慢数据可以先发 HTML 骨架,数据好了再流式传输补上。用户不用等所有数据加载完。

部署自由

Start 构建出标准产物,可以部署到任何平台:

  • Vercel
  • Netlify
  • Cloudflare
  • AWS
  • Fly.io
  • Railway
  • 你自己的服务器

不锁定平台,同一份代码到处跑。

22.7 第一个 Server Function 体验

来个简单的计数器,感受一下 Server Functions:

// src/routes/index.tsx
import * as fs from 'node:fs'
import { createFileRoute, useRouter } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'

const filePath = 'count.txt'

// 读取计数(服务端执行)
async function readCount() {
  return parseInt(
    await fs.promises.readFile(filePath, 'utf-8').catch(() => '0'),
  )
}

// GET 服务器函数:读取当前计数
const getCount = createServerFn({
  method: 'GET',
}).handler(() => {
  return readCount()
})

// POST 服务器函数:更新计数
const updateCount = createServerFn({ method: 'POST' })
  .validator((d: number) => d)
  .handler(async ({ data }) => {
    const count = await readCount()
    await fs.promises.writeFile(filePath, `${count + data}`)
  })

export const Route = createFileRoute('/')({
  component: Home,
  loader: async () => await getCount(),
})

function Home() {
  const router = useRouter()
  const count = Route.useLoaderData()

  return (
    <button
      type="button"
      onClick={() => {
        updateCount({ data: 1 }).then(() => {
          // 刷新 loader 数据
          router.invalidate()
        })
      }}
    >
      Add 1 to {count}?
    </button>
  )
}

这段代码做了什么:

  1. getCount 是服务端函数,读取文件里的数字
  2. updateCount 是服务端函数,把数字加 1 写回文件
  3. loader 调用 getCount 加载初始数据
  4. 点击按钮调用 updateCount,然后 router.invalidate() 刷新

客户端代码调用的 updateCountgetCount,实际在服务端执行。类型安全,输入有校验。

Tip

这个例子用了 Node.js 的 fs 模块读写文件。Server Functions 里可以用任何服务端 API(数据库、文件系统、进程等),这些代码不会打包到客户端。

22.8 小结

这一章认识了 TanStack Start:

  • 定位:基于 Router + Vite 的全栈 React 框架
  • 与 Router 的关系:Start 100% 依赖 Router 做路由,在此基础上加了 SSR、Server Functions 等
  • 与 Next.js 的区别:Start 是开发者优先(显式控制、类型安全、部署自由),Next.js 是平台优先(Vercel 集成、隐式优化)
  • 项目结构src/routes/ 放路由,src/router.tsx 配路由器,vite.config.ts 配构建
  • 根路由:要渲染完整 HTML 文档,包含 <HeadContent /><Scripts />
  • Server Functions:服务端定义函数,客户端类型安全调用

下一章深入 Server Functions,搞懂 createServerFn 的完整用法。