TanStack Start 中文文档
快速开始

TanStack Start 对比 Next.js

在 TanStack Start 和 Next.js 之间做选择,不只是对比特性列表——关键在于理解每个框架各自优化了什么,以及这些决策会如何影响你整个开发体验。

本页解释两者的根本差异,澄清常见误解,并帮你做出明智的决定。想看逐特性的对照表,请阅读完整对比表格。准备好了想切换?请看迁移指南

**关于基准测试(benchmark)的说明:**如果有人说出了 Start 与 Next 的性能对比数字,却没有说明方法论、应用复杂度、托管细节和具体配置,那这些数字毫无意义。对比必须建立在双方都采用最佳实践的前提上。你不能一边让 Next 享受最佳实践,一边默认 Start 用户各种配置错误。

两种不同的 React 愿景

两个框架都想帮你构建优秀的 React 应用,但它们对「优秀」的理解建立在不同的假设之上。

Next.js:平台路线

Next.js 为 Vercel 对 Web 的愿景而优化:服务端优先渲染、深度平台集成,以及在其基础设施上效果最好的自动化优化。

核心设想:大多数 Web 内容是静态或近乎静态的。Server Components 应该成为默认。交互性是需要你主动选择的例外。框架应该替你做出决策,而这些决策应当为它们的基础设施而优化。

如果你的情况符合以下几点,这条路线很适合:

  • 你部署在 Vercel 上,想要紧密的平台集成
  • 你的应用是内容为主、夹杂交互岛(islands of interactivity)
  • 你希望框架替你做出架构决策

TanStack Start:开发者优先路线

TanStack Start 为开发者的掌控力和正确性而优化:处处类型安全、显式优于隐式、可组合的原语、部署自由。

核心设想:开发者比框架更了解自己的应用。服务端渲染是在合适的地方由你主动选择的优化,而不是默认。框架应该给你强大的原语,然后让开路。

如果你的情况符合以下几点,这条路线很适合:

  • 你想让「部署到哪里」成为你自己做的决定,而不是框架替你决定
  • 你的应用交互性很强
  • 你重视类型安全和显式控制
  • 你想清楚地知道自己的代码到底在做什么

心智模型上的鸿沟

这是最重要的差异,其他一切都由此展开。

都支持 SSR,但交互性的默认值不同

先说清楚:两个框架都默认 SSR,也都支持静态生成和 React 服务器组件(React Server Components,RSC)。区别不在于能力本身,而在于你如何获得这些能力,以及你拥有多少控制权。

Next.js 默认使用 Server Components。除非你加上 "use client",否则每个组件都是 Server Component。Server Components 不能使用 state、effect 或事件处理器——因此走向交互性的路径,要求你理解框架的隐式边界、缓存层级和序列化规则。

TanStack Start 默认使用交互式组件(传统 React)。你的组件默认就支持 SSR 和水合(Hydration),随时可以使用 state 和事件处理器。只有在你认为 Server Components 能带来价值时才主动选用——比如承载大量静态内容、把密钥留在服务端,或减小打包体积。

两条路线都能到达同样的终点。问题是:对_你的_应用而言,哪个方向更像是逆流而上?

想想看:

  • 如果你的大多数组件都需要 state、effect 或事件处理器,Next 的默认值意味着你要不停地写 "use client" 标注,并反复思考序列化边界
  • 如果你的大部分内容是静态的,Start 让你显式地把这些组件纳入服务端渲染——对缓存和水合的控制也更清晰
  • 无论如何,Start 都给你更细粒度的控制:不只是控制_在哪里_渲染,还控制_如何_渲染

隐式与显式

两个框架都处理好了基础问题——代码分割、缓存、SSR、静态优化。区别在于可见性和可预测性。Next.js 叠了一层隐式行为:多层服务端缓存(有着反复破坏性变更的前科,以及社区的不满)、与文件结构绑定的数据获取约定、需要理解框架内部才能覆盖的优化。

TanStack Start 显式但不过度啰嗦。加载器(Loader)函数、缓存配置、中间件链——这些都明明白白写在你的代码里,而不是藏在约定背后。这不是说代码更多,而是说你写的代码直接对应运行时发生的事情。

架构深入

构建管线

Next.js 使用自研构建系统(历史上是 Webpack,现在转向 Turbopack)。它与自身的架构深度集成,带来了优化能力,但牺牲了灵活性。Turbopack 改善了开发速度,但依然比不过 Vite 或 Rsbuild。

TanStack Start 支持 Vite 和 Rsbuild,这意味着:

  • 更快的开发服务器启动
  • 更快的 HMR
  • 可以接入现代插件生态
  • 标准工具链,可以迁移到其他项目

运行时

Next.js 附带了一个相当庞大的运行时,用于支撑 Server Components、服务端缓存、自动化优化以及 App Router 的各种约定。这个运行时有实实在在的重量。

TanStack Start 只附带一个极简运行时。路由器强大但精简。服务器函数(Server Functions)只是薄薄的一层 RPC 封装。在你和 React 之间,没有框架魔法层。

打包体积不是一切,但它是其他一切的基础。Start 的架构设计目标就是最小化运行时开销——即使加上 RSC 支持,运行时依然保持精简。Next 打包体积的很大一部分是「架构税」,而不是特性本身的分量。

类型系统

Next.js 有 TypeScript 支持。TanStack Start 是围绕 TypeScript 构建的。

区别很重要:

  • 在 Next.js 中,客户端与服务器之间的边界会造成类型缺口。Server Actions 可能接收到与 TypeScript 预期不符的数据。你需要运行时校验才能保证安全。
  • 在 Start 中,服务器函数具备端到端类型安全。输入校验、返回类型、中间件上下文、路由参数——全部在编译期检查。

在 AI 辅助开发的时代,端到端类型安全应该是不可妥协的底线。类型不只是「对 DX 友好」——它们是对正确性的保证,能防止线上错误。

路由

这是 TanStack 最耀眼的地方。为 Start 提供动力的 TanStack Router,是任何框架中最强大、最类型安全的路由器。

Next.js 没有的特性:

  • 类型安全的搜索参数(Search Params)——以完整的类型推断定义、校验和解析搜索参数。Next 给你的是 ?foo=bar 这样的字符串;Start 给你的是经过校验、带类型的对象
  • 搜索参数校验与中间件——在路由层面转换和校验搜索参数
  • 类型安全的路径参数(Path Params)——路径参数会被推断和校验,而不只是 string | string[]
  • 类型安全的路由上下文——在嵌套路由之间传递带类型的上下文
  • 路径参数校验与自定义解析——把 /users/123 解析为数字,并校验它确实存在
  • 路由挂载/切换/卸载事件——在路由生命周期上挂钩子
  • 基于代码的路由——文件路由不适用时,可以用代码定义路由
  • 内置开发者工具——检查路由器状态、缓存和挂起的导航
  • 元素级滚动恢复——为特定元素恢复滚动位置,而不仅是页面
  • 导航拦截——用 useBlocker 提示未保存的更改

真正有效的类型安全:

  • 路由路径完全类型化——拼写错误就是编译错误
  • 链接会校验目标——生产环境没有死链
  • 加载器知道自己所在路由的上下文——不用猜测能拿到什么数据
  • 搜索参数端到端类型化——从 URL 到组件

Next.js 有可用的基于文件的路由,但它的类型安全是表面功夫(不过是给链接提示的 IDE 插件),与 TanStack Router 的编译期保证相比差远了。

一等公民集成:

TanStack Router 从设计之初就为同构(isomorphism)和水合而打造。这使它成为与 TanStack Query 以及其他数据获取库进行一流集成的基石。在 Next 中,你要手动接线 Query;在 Start 中,这是官方集成支持的既定模式。

完整的特性矩阵请看完整的 Router 对比

缓存问题

缓存是两种哲学差异最明显的地方。

Next.js:隐式且偏服务端

Next.js 默认激进缓存(或者曾经如此——他们改过好几次了)。缓存发生在多个层级:

  • 请求记忆化(request memoization)
  • 数据缓存(data cache)
  • 完整路由缓存(full route cache)
  • 路由器缓存(router cache)

每一层都有自己的失效(invalidation)语义。这套系统被重写过多次,并因不可预测而饱受社区诟病。Next 15 简化了默认值,但服务端 RSC 流缓存的基础复杂性依然存在。

当人们谈论「组件缓存」时,他们指的是在服务器上缓存序列化后的 RSC 流。这比大多数人想象的复杂得多:

  • 你缓存的是带有复杂失效语义的文本流
  • 这发生在 Lambda 式环境中,而这类环境有自己的约束
  • 细粒度的失效需要显式设置标签(tagging)
  • 缓存未命中会以不可预测的方式在组件树中级联

问问任何一个声称服务端组件缓存很特别的人:「当数据依赖发生变化时,你如何使缓存的 RSC 流失效?」大多数人都答不清楚。

TanStack Start:RSC 不过是数据

Start 把 Server Components 的输出,当作流经你应用的其他任何数据一样对待。没有带特殊语义的「组件缓存」——RSC 负载就是数据,你可以随意缓存数据:

  • TanStack Router——内置 SWR 风格缓存,带 staleTimegcTime
  • TanStack Query——功能完备的异步状态管理
  • CDN 缓存——边缘的标准 HTTP 缓存头
  • Redis、数据库、随便什么——它只是数据,用你现有的基础设施即可
export const Route = createFileRoute('/posts/$postId')({
  loader: async ({ params }) => fetchPost(params.postId),
  staleTime: 10_000, // Fresh for 10 seconds
  gcTime: 5 * 60_000, // Keep in memory for 5 minutes
})

这与 TanStack Query 在数百万应用中久经考验的 SWR 模式如出一辙。没有新的心智模型,不用学习框架特有的缓存语义,也不用追着「鬼影」调试。

各个缓存层都是成熟的,并且能自然组合:

  • 客户端 SWR 缓存(内置于路由器)
  • 边缘 CDN 缓存(标准 HTTP 语义)
  • 需要时按需服务端缓存(显式、主动选择)

你本来就知道如何缓存数据。Start 不会逼你学一套新方法。

RSC 的最终结论

Start 对 RSC 的支持完全齐备,与 Next.js 特性对等。区别在于心智模型和认知负担。

在 Next 中,RSC 是一种范式——你围绕它构建、时刻思考它、到处管理它的边界。在 Start 中,RSC 只是另一种数据原语。用你在 TanStack Query 和 Router 中早已熟悉的模式去获取它、缓存它、组合它。

我们把这种方法称为复合组件(Composite Components)——由服务器生成、客户端可以获取、缓存、流式传输并组装的 React 组件。组合权在客户端;服务器只负责交付 UI 片段。没有新的心智模型,没有框架特有的缓存语义,只是让数据流过你已熟悉的工具。

想深入了解,请看复合组件:客户端主导组合的 Server Components

服务器函数 vs Server Actions

两个框架都允许你从客户端调用服务器代码,但方式差异很大。

Next.js Server Actions

'use server'

export async function createPost(formData: FormData) {
  const title = formData.get('title')
  // No compile-time type safety on inputs
  return db.posts.create({ title })
}

Server Actions 与表单和 transitions 集成良好。简单场景下很方便,但在边界处提供的类型安全很有限。

TanStack 服务器函数

export const createPost = createServerFn({ method: 'POST' })
  .validator(z.object({ title: z.string().min(1) }))
  .middleware([authMiddleware])
  .handler(async ({ data, context }) => {
    // data is typed and validated
    // context comes from middleware, also typed
    return db.posts.create({ title: data.title })
  })

服务器函数给你:

  • 输入校验——定义 schema,获得运行时校验和编译期类型
  • 中间件——可组合,同时支持客户端与服务端
  • 完整的类型推断——从输入到输出再到错误处理
  • 显式 RPC——对正在发生的事情有清晰的心智模型

安全说明: Start 的架构不会在服务器上解析 Flight 数据——负载是单向流动的(从服务器到客户端)。最近关于 RSC 序列化漏洞的 React 安全公告不适用于 Start 的模型。

Next.js 占优的地方

Next.js 存在的时间更长。这不算技术优势,但它带来了实实在在的生态效应:

更多内容——更多教程、更多 Stack Overflow 答案、更多博客文章、更多示例仓库。当你在 Google 上搜问题,更可能搜到 Next 专属的答案。

心智占有率——Next 是很多圈子里默认的推荐。这意味着更多开发者用过它,也就有更多内容,从而强化这个循环。

Vercel 集成——Next.js 由 Vercel 开发,所以 Vercel 的新平台特性往往优先支持 Next.js。话虽如此,Start 在 Vercel 上也能完美运行——选择 Start 并不意味着放弃预览部署或边缘函数(edge functions)。只是你不会被锁定在「Vercel 是唯一的一等公民」里。

内置图片/字体优化——Start 通过可插拔方案支持图片优化(比如 Unpic),但不是内置的。「内置」是否好于「可插拔」,取决于你是否想让框架替你做出这个选择。

这些都不是技术上的优越性——它们是生态和商业模式上的优势。就技术本身而言,我们对 Start 的方案有信心。

Start 更好的地方

既然支持 RSC,问题就不再是「Start 缺什么?」,而是:「为什么要为了 Next 的 API 设计,放弃 Start 的路由器、类型安全、缓存设计和更简单的心智模型?」

类型安全——端到端的,而不是事后贴上去的。这能防止 bug,并让你可以自信地重构。

路由——任何框架中最强大、最类型安全的路由器。搜索参数、路径参数、加载器、中间件——全部完全类型化。

缓存模型——如果你用过 TanStack Query,你本来就理解这些显式的 SWR 原语。没有需要调试的隐式层级。

开发体验——Vite 和 Rsbuild 很快。即时启动、快速 HMR、更低资源占用。这在一天的工作中不断累积——当 AI 代理在高频迭代你的代码时更是如此。

把部署当作特性——Start 把部署灵活性当作一等公民特性。Cloudflare、Netlify、AWS、Fly、Railway、你自己的服务器——全都平等支持。你的应用在任何地方行为一致,因为它建立在标准之上,而不是平台专属优化之上。这意味着你可以:

  • 根据价格、性能或与用户的距离来选择托管
  • 切换服务商时无需重写部署逻辑
  • 在不同平台之间用同一套代码跑开发、预发布和生产
  • 避免积累那些日后制造摩擦的平台专属模式

中间件——可组合的中间件,既作用于请求层面,也作用于服务器函数层面,客户端与服务端皆可。

调试——可预测的执行。出问题时你能追踪到根因。没有隐藏行为的抽象层。

总结

方面TanStack StartNext.js
理念开发者掌控,显式模式平台集成,约定优先
组件默认交互式,主动选用 RSC默认 Server Components
类型安全端到端、编译期有 TypeScript 支持,但边界有缺口
服务器函数类型化、可校验、支持中间件边界无类型、无中间件
缓存显式 SWR 原语多层隐式缓存
构建工具Vite 或 RsbuildTurbopack/Webpack
部署处处平等支持为 Vercel 优化
路由顶尖类型安全基于文件、基础类型
RSC支持支持
成熟度2 年以上,接近 1.08 年以上,API 历史上不稳定

准备好试试 Start 了?

查看快速上手指南,或阅读从 Next.js 迁移一文。

On this page