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 风格缓存,带
staleTime和gcTime - 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 Start | Next.js |
|---|---|---|
| 理念 | 开发者掌控,显式模式 | 平台集成,约定优先 |
| 组件 | 默认交互式,主动选用 RSC | 默认 Server Components |
| 类型安全 | 端到端、编译期 | 有 TypeScript 支持,但边界有缺口 |
| 服务器函数 | 类型化、可校验、支持中间件 | 边界无类型、无中间件 |
| 缓存 | 显式 SWR 原语 | 多层隐式缓存 |
| 构建工具 | Vite 或 Rsbuild | Turbopack/Webpack |
| 部署 | 处处平等支持 | 为 Vercel 优化 |
| 路由 | 顶尖类型安全 | 基于文件、基础类型 |
| RSC | 支持 | 支持 |
| 成熟度 | 2 年以上,接近 1.0 | 8 年以上,API 历史上不稳定 |
准备好试试 Start 了?
查看快速上手指南,或阅读从 Next.js 迁移一文。