Documentation

为何是 TanStack Start

我们选择 TanStack Start 而非 Next.js 的原因

Next.js 是成熟的框架,生态完善。我们选择 TanStack Start,主要是因为它在渲染策略和类型安全上更符合我们的需求。

这不是一篇批评 Next.js 的文章。Next.js 的向后兼容性做得很好。


TanStack Start 2026:不只是 SPA 框架

很多人对 TanStack Start 的印象还停留在"客户端优先的 SPA 框架"。2026 年的 TanStack Start 已经支持完整的服务端渲染能力:

  • Full-document SSR:完整的服务端渲染
  • Streaming:流式渲染
  • Static Prerendering:静态预渲染
  • ISR(增量静态再生成):动态内容的静态化方案
  • Selective SSR:按需选择哪些页面需要 SSR
  • SPA Mode:纯客户端渲染模式
  • Server Functions:类型安全的服务端函数调用
  • LLM Optimization (LLMO):针对 AI 爬虫的优化

Next.js 能做的渲染方式,TanStack Start 基本都支持了。那我们为什么还是选了它?


Next.js 的学习曲线

先说说 Next.js 的学习成本。新手往往只是想把产品做出来,但 App Router 引入的概念确实不少。

概念复杂度

Next.js App Router 引入了这些概念:

  • Server Components vs Client Components:默认是服务端组件,需要 "use client" 切换
  • 序列化边界:不能在 Server Component 中传递函数、类实例给 Client Component
  • 水合错误(Hydration Mismatch):服务端和客户端渲染不一致时很难调试
  • "use server" 指令:一个字符串变成服务器组件或者服务端函数

新手光是理解"这行代码在哪里执行"就够头疼的。

缓存机制

Next.js 的缓存系统比较复杂:

// 这个 fetch 会被缓存吗?
const data = await fetch('/api/data');

// 加上这个呢?
const data = await fetch('/api/data', { cache: 'no-store' });

// 还是需要这个?
export const dynamic = 'force-dynamic';

// 或者用 revalidate?
export const revalidate = 60;

Next.js 有多层缓存:Data Cache、Full Route Cache、Router Cache... 它们的交互方式连资深开发者也经常搞混。v14 默认缓存,v15 又改成默认不缓存,这种变化增加了理解成本。

两套路由系统

Next.js 同时存在 Pages Router 和 App Router:

  • 网上教程混杂,新手不知道该学哪个
  • 两套系统的概念和 API 差异大
  • 从 Pages Router 迁移到 App Router 需要改动不少代码

调试

Next.js 的隐式行为让调试变得麻烦:

  • 页面没更新?可能是缓存问题,但不知道是哪层
  • 组件报错?可能是服务端/客户端边界问题,错误信息不够清晰
  • 性能问题?不确定是 SSR、水合还是客户端渲染的问题

TanStack Start 的特点

TanStack Start 的学习曲线相对平缓:

  • 没有 Server Components:所有组件都是你熟悉的 React 组件
  • 显式的渲染策略:每个路由的 SSR/SPA/静态行为在代码中明确定义
  • 熟悉的数据获取:TanStack Query 的 useQuery 模式
  • 类型系统帮你检查:错误在编译时暴露

TanStack Start 也有学习成本,但它的心智模型更接近传统 React 开发。


我们选择它的原因

1. 渲染策略由你决定

Next.js 的设计是"服务端优先"——默认所有组件都是 Server Components,需要显式声明 "use client" 切换到客户端。

TanStack Start 不同:你来决定每个页面的渲染策略。

// 纯 SPA 模式:适合仪表盘、AI 聊天等高交互应用
export const Route = createFileRoute('/dashboard')({
  ssr: false,
  component: Dashboard,
});

// SSR 模式:适合需要 SEO 的页面
export const Route = createFileRoute('/blog/$slug')({
  loader: async ({ params }) => fetchPost(params.slug),
  component: BlogPost,
});

// 静态预渲染:适合内容不常变化的页面
export const Route = createFileRoute('/about')({
  preload: 'intent',
  component: About,
});

你可以在同一个应用中混用不同的渲染策略,不被框架的默认行为限制。

2. 类型安全的路由系统

TanStack Router 的类型安全做得很好:

// 文件名:$threadId.tsx
export const Route = createFileRoute('/chat/$threadId')({
  loader: async ({ params }) => {
    // params.threadId 的类型自动推断为 string
    return fetchThread(params.threadId);
  },
  component: () => {
    const { threadId } = Route.useParams();
    // 编译时就知道 threadId 一定存在
  }
});

重命名文件时,开发服务器会自动更新相关代码。链接拼错或参数类型不匹配会在编译时报错。

3. Server Functions vs Server Actions

两者都允许从客户端调用服务器代码,设计上有区别:

// TanStack Server Function:类型安全的 RPC
export const updateUser = createServerFn()
  .validator((data: { name: string; age: number }) => data)
  .handler(async ({ data }) => {
    // data 的类型自动推断
    return db.users.update(data);
  });

// 客户端调用(完全类型安全)
await updateUser({ name: 'Alice', age: 30 });

Server Functions 更像类型安全的 RPC,内置验证器和中间件支持,调用方式和普通函数一样。

4. Vite 构建

TanStack Start 基于 Vite 构建,开发体验比 Webpack 时代好不少:

维度Next.js + TurbopackTanStack Start + Vite
启动速度大型项目 5-10 秒毫秒级
HMR 响应偶尔卡顿基本即时
插件生态需适配直接使用 Vite 插件

技术基础

TanStack Start 是新框架,但构建在成熟的技术上:

  • TanStack Router:类型安全路由
  • TanStack Query:React 数据获取库
  • Vite:快速的开发构建
  • Nitro:来自 Nuxt 团队的服务端基础设施

挑战

早期框架的代价

TanStack Start 是较新的框架,API 已稳定但可能存在边缘情况。

相比 Next.js 多年积累的文档和社区解答,TanStack Start 的资料还在完善中。遇到问题时,你可能需要直接看源码或在 Discord 里提问。


Next.js 的优点

Next.js 仍然是好框架:

  • 向后兼容性好:Next 10 的应用可以升级到 Next 16,改动不大
  • 稳定:多年持续维护
  • 生态成熟:部署、监控、边缘计算都有成熟方案

建议

继续用 Next.js

  • 团队熟悉 Next.js,项目运行正常
  • 需要 React Server Components
  • 不想踩新框架的坑

考虑 TanStack Start

  • 构建交互密集型应用(仪表盘、AI 聊天、协作工具)
  • 看重端到端的类型安全
  • 想要更灵活的渲染策略
  • 能接受早期框架的文档和生态不完善

总结

我们选 TanStack Start 不是因为 Next.js 不好,而是它更符合我们的需求:

  • 灵活的渲染策略,同一应用可以混用 SSR、SPA、静态预渲染
  • 端到端的类型安全
  • 行为显式,没有隐藏的魔法

选什么框架取决于你的需求。了解清楚需求,选合适的工具就行。

目录