00 / 00

选择 Next.js 还是 Tanstack?

记录我的技术架构,为什么选择 Tanstack + hono + oRPC

为什么我们想用 TanStack + Hono + oRPC,替代传统 Next.js 技术栈

我们的目标不是“为了新而新”,而是因为我们想做的是:

多端应用 + Cloudflare-first 部署 + 快速 MVP + 长期可维护 API。

传统 Next.js 很强,但它更像一个“大而全 Web 全栈框架”。 而我们现在更想要的是:

前端页面:TanStack Start
后端 HTTP/API:Hono
长期 API 合约:oRPC
部署平台:Cloudflare Workers

这套组合的核心价值是:

开发快、访问快、部署轻、多端友好、API 可长期复用。

TanStack Start 官方定位是基于 TanStack Router 的全栈 React 框架,提供 SSR、Streaming、Server Functions、Bundling 等能力;Cloudflare 也已经提供 TanStack Start 的 Workers 部署指南。(TanStack) Hono 自带 RPC 能力,可以通过共享 API specification 让客户端推断输入输出类型;oRPC 则提供 Hono Adapter,更偏向“类型安全 API 合约层”。(Hono)


一、Server Function / Hono API / oRPC 对比

1. 架构决策视角:看痛点和收益

类型TanStack Server FunctionHono APIoRPC
一句话理解页面里直接写后端函数轻量高性能 API 框架类型安全 API 合约层
解决的核心痛点最快把功能做出来后端接口清晰、性能好、Cloudflare 适配好多端调用不失控,API 能长期复用
最大优势开发快,少写接口样板代码轻、快、标准 HTTP、适合 Webhook/API类型安全、接口规范、适合多端和 OpenAPI
对新手友好度高,像写本地函数中,需要理解 API 路由中,需要理解 procedure / contract
对老板的价值快速上线 MVP系统边界清晰,未来好扩展API 成为资产,方便小程序/App/Agent 复用
对 Cloudflare 友好度好,跟 TanStack Start 走非常好好,通常挂在 Hono 上
适合多端吗一般,更偏 Web 内部可以,但需要自己维护类型和文档非常适合
适合长期产品吗适合页面私有逻辑适合后端服务适合核心业务 API
主要风险逻辑和页面耦合类型、文档、错误格式容易散多一层抽象,小项目可能重
最适合场景MVP、表单提交、页面私有数据Webhook、支付、AI 接口、REST API小程序、App、Tauri、AI Agent、开放 API
我的建议默认用于页面内部逻辑默认作为后端入口用于长期、多端、核心业务 API

一句话:

Server Function 负责快,Hono 负责稳,oRPC 负责长期可复用。


2. 工程师视角:看技术边界

类型TanStack Server FunctionHono APIoRPC
技术定位Full-stack 框架里的 server-only 函数HTTP server / routerRPC + API contract
入口形态createServerFn()app.post('/api/posts')router.posts.create
前端调用方式直接调用函数fetch() 或封装 clientclient.posts.create()
输入校验可以用 Zod/Valibot通常配合 Zod Validator通常内置在 procedure/contract 中
输出类型TypeScript 可推断默认不自动推断,除非用 Hono RPC 或手写类型自动推断
错误处理项目内自定义自己统一更容易统一成 API 层规范
OpenAPI不是重点可通过生态实现核心优势之一
业务语义更像页面动作更像 HTTP endpoint更像业务动作
多端复用弱一些中等
Webhook 适配一般可以,但通常仍由 Hono 承载入口
文件上传/流式响应可以,但不是最自然自然可做业务层,不一定管底层细节
与 Cloudflare Workers非常好通过 Hono/HTTP adapter 承载
项目复杂度中低中高
最佳使用边界页面私有逻辑HTTP/API 边界核心业务 API 合约

二、Hono RPC vs oRPC 单独对比

1. 架构决策视角

类型Hono RPCoRPC
一句话理解Hono 自带的类型安全调用方式更正式的类型安全 API 合约系统
核心价值轻量、简单、少依赖长期、多端、文档化、API 资产化
适合谁个人项目、小团队、Web 前后端都用 TS长期产品、多端应用、ToB、Agent、小程序
最大优势学习成本低,直接复用 Hono 路由类型API 语义更清楚,更适合跨端和开放接口
最大痛点更偏 TypeScript 内部项目多一层抽象,早期 MVP 会重一点
是否适合多端一般,主要适合 TS 客户端非常适合
是否适合 OpenAPI不是核心定位核心价值之一
是否适合快速 MVP适合可以,但不是必要
是否适合长期模板可以更适合
我的建议小项目先用 Hono RPC长期模板/多端项目上 oRPC

一句话:

Hono RPC 是“轻量内用”;oRPC 是“正式 API 合约”。


2. 工程师视角

类型Hono RPCoRPC
类型来源从 Hono AppType 推导从 router / procedure / contract 推导
API 组织方式HTTP 路由优先业务 procedure 优先
调用风格client.api.posts.$post()client.posts.create()
路由语义偏 HTTP path/method偏业务动作
前后端耦合前端需要引用 Hono app 类型前端引用 oRPC router/client 类型
TypeScript 项目适配很好很好
非 TS 客户端一般更好
OpenAPI / API 文档可额外做,但不是核心天然更适合
中大型项目结构可能受 Hono 路由结构影响更容易按业务模块组织
Monorepo 配置需要处理 AppType 引用也需要处理类型导出,但边界更合约化
学习成本
最适合内部 Web API多端复用 API / 对外 API

Hono 官方文档里的 RPC 模式是通过 hc<AppType>() 创建客户端,让客户端基于服务端类型推断接口输入输出;oRPC 文档则单独提供 Hono Adapter,说明它通常作为一层 API 合约挂在 Hono 之上。(Hono)


三、Next.js vs TanStack Start 对比

1. 架构决策视角

类型Next.jsTanStack Start
一句话理解成熟的大而全 React 全栈框架更轻、更快、更 Cloudflare-friendly 的 React 全栈框架
核心优势生态成熟、模板多、Vercel 最顺开发快、性能好、Vite 体系、Cloudflare 适配更自然
最大痛点臃肿、热重载慢、心智负担重、非 Vercel 部署容易踩坑生态较新,模板和案例少于 Next.js
开发体验项目大后容易重更轻快
访问性能强,但依赖部署平台和配置适合边缘部署和轻量运行时
Cloudflare 部署能做,但经常需要适配Cloudflare 已有官方部署指南
多端 API 友好度Server Actions 偏 Web 内部配合 Hono/oRPC 更适合多端
内容站 / SEO非常成熟可以,但生态不如 Next.js
SaaS 模板非常多较少,需要自己搭
适合快速 MVP可以,但偏重非常适合
适合长期 OPC 模板可以,但维护成本高更符合个人/小团队快速创造
老板视角结论稳,但重轻、快、适合 Cloudflare-first 和多端战略

一句话:

Next.js 适合“成熟 Web 项目”;TanStack Start + Hono + oRPC 更适合“Cloudflare-first 多端产品”。


2. 工程师视角

类型Next.jsTanStack Start
构建体系Next 自有体系 / Turbopack / Webpack 生态Vite 体系
路由App Router / Pages RouterTanStack Router
类型安全路由一般
服务端逻辑Route Handler / Server Actions / RSCServer Functions / Loader / Middleware
数据加载模型RSC、fetch cache、revalidate、Server ActionsLoader、Server Functions、Query 组合
心智负担中低
热重载体验大项目容易慢通常更快
Vercel 部署最佳体验可以
Cloudflare 部署可行,但通常需要额外适配路径更自然
Edge/Node 差异容易踩 runtime 差异更偏 Web runtime 思路
API 多端复用Route Handler 可做,但 Server Actions 不适合多端配合 Hono/oRPC 更清楚
AI 辅助开发资料多,但框架规则复杂结构清晰,但生态资料较少
生态成熟度非常成熟新兴
适合内容站
适合交互应用强,但偏重强且轻
适合 Cloudflare-first一般

四、为什么不是只用 TanStack Server Function?

因为我们确实想做多端。

Server Function 很适合 Web 页面内部逻辑,比如:

保存用户设置
提交表单
读取当前页面 dashboard 数据

但它不适合作为长期多端 API 的唯一方案。

如果未来有:

Web
微信小程序
Tauri 桌面端
移动 App
AI Agent
第三方脚本
开放 API

那我们需要一个更清晰的 API 层。

所以边界应该是:

类型用在哪里
TanStack Server Function页面私有逻辑
Hono APIHTTP/API/Webhook 入口
oRPC多端复用的核心业务 API

五、为什么不是只用 Hono API?

只用 Hono API 当然可以。

问题是,随着接口变多,你会开始遇到这些问题:

前端不知道接口要传什么
接口返回值靠人脑记
错误格式不统一
API 文档没人维护
小程序/App/Agent 又要重新对接

Hono 解决的是:

怎么接收 HTTP 请求。

oRPC 解决的是:

这个 API 的输入、输出、错误、文档、客户端调用方式如何长期保持一致。

所以我们不是用 oRPC 替代 Hono,而是:

Hono 负责 HTTP 边界
oRPC 负责 API 合约

六、为什么不是只用 Hono RPC?

Hono RPC 很好,尤其适合小项目。

但它更适合:

我的 Web 前端 调用 我的 Hono 后端

而我们想要的是:

Web
小程序
App
Tauri
AI Agent
外部脚本
第三方系统

所以我们更关心:

API 文档
OpenAPI
错误格式
多端调用
长期合约
业务模块化

这就是 oRPC 更合适的地方。


七、相同功能代码示例:创建一篇 Post

下面都实现同一个功能:

createPost({
  title: string
  content: string
})

返回:

{
  id: string
  title: string
  content: string
  createdAt: string
}

为了简化,数据库用一个假函数:

// db/posts.ts
export async function insertPost(input: {
  title: string
  content: string
}) {
  return {
    id: crypto.randomUUID(),
    title: input.title,
    content: input.content,
    createdAt: new Date().toISOString(),
  }
}

方案 A:TanStack Server Function

server:定义 server function

// app/server/posts.ts
import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'
import { insertPost } from './db/posts'

const CreatePostSchema = z.object({
  title: z.string().min(1),
  content: z.string().min(1),
})

export const createPost = createServerFn({ method: 'POST' })
  .validator((data) => CreatePostSchema.parse(data))
  .handler(async ({ data }) => {
    const post = await insertPost(data)
    return post
  })

client:页面中直接调用

// app/routes/posts/new.tsx
import { useState } from 'react'
import { createPost } from '@/server/posts'

export default function NewPostPage() {
  const [title, setTitle] = useState('')
  const [content, setContent] = useState('')

  async function onSubmit(e: React.FormEvent) {
    e.preventDefault()

    const post = await createPost({
      data: {
        title,
        content,
      },
    })

    console.log('created post:', post)
  }

  return (
    <form onSubmit={onSubmit}>
      <input
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder="标题"
      />

      <textarea
        value={content}
        onChange={(e) => setContent(e.target.value)}
        placeholder="内容"
      />

      <button type="submit">创建文章</button>
    </form>
  )
}

适合场景

页面内部表单
Dashboard 操作
快速 MVP
不需要给小程序/App/Agent 调用

优点是:

最少代码
最快开发
类型体验好

缺点是:

不是正式 API 合约
多端复用不自然

方案 B:Hono API

server:定义 Hono API

// server/app.ts
import { Hono } from 'hono'
import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'
import { insertPost } from './db/posts'

const app = new Hono()

const CreatePostSchema = z.object({
  title: z.string().min(1),
  content: z.string().min(1),
})

app.post(
  '/api/posts',
  zValidator('json', CreatePostSchema),
  async (c) => {
    const input = c.req.valid('json')
    const post = await insertPost(input)

    return c.json(post)
  },
)

export default app

client:前端 fetch 调用

// app/routes/posts/new.tsx
import { useState } from 'react'

type Post = {
  id: string
  title: string
  content: string
  createdAt: string
}

export default function NewPostPage() {
  const [title, setTitle] = useState('')
  const [content, setContent] = useState('')

  async function onSubmit(e: React.FormEvent) {
    e.preventDefault()

    const res = await fetch('/api/posts', {
      method: 'POST',
      headers: {
        'content-type': 'application/json',
      },
      body: JSON.stringify({
        title,
        content,
      }),
    })

    if (!res.ok) {
      throw new Error('创建文章失败')
    }

    const post = (await res.json()) as Post

    console.log('created post:', post)
  }

  return (
    <form onSubmit={onSubmit}>
      <input
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder="标题"
      />

      <textarea
        value={content}
        onChange={(e) => setContent(e.target.value)}
        placeholder="内容"
      />

      <button type="submit">创建文章</button>
    </form>
  )
}

适合场景

Webhook
支付回调
AI 接口
上传接口
后台 API
标准 HTTP 服务

优点是:

边界清楚
标准 HTTP
Cloudflare 友好
任何客户端都能调用

缺点是:

前端类型需要自己维护
返回值容易靠人脑记
错误格式容易不统一

方案 C:Hono + oRPC

下面是偏“结构示意”的写法,具体 API 细节以项目安装的 oRPC 版本为准。核心思想是:业务 API 先定义成 procedure,再挂到 Hono 上暴露。

shared schema

// rpc/schemas/post.ts
import { z } from 'zod'

export const CreatePostSchema = z.object({
  title: z.string().min(1),
  content: z.string().min(1),
})

export const PostSchema = z.object({
  id: z.string(),
  title: z.string(),
  content: z.string(),
  createdAt: z.string(),
})

server:定义 oRPC router

// rpc/router.ts
import { os } from '@orpc/server'
import { insertPost } from '../server/db/posts'
import { CreatePostSchema, PostSchema } from './schemas/post'

export const postsRouter = {
  create: os
    .input(CreatePostSchema)
    .output(PostSchema)
    .handler(async ({ input }) => {
      const post = await insertPost(input)
      return post
    }),
}

export const appRouter = {
  posts: postsRouter,
}

export type AppRouter = typeof appRouter

server:挂到 Hono

// server/app.ts
import { Hono } from 'hono'
import { RPCHandler } from '@orpc/server/fetch'
import { appRouter } from '../rpc/router'

const app = new Hono()

const handler = new RPCHandler(appRouter)

app.use('/rpc/*', async (c, next) => {
  const { matched, response } = await handler.handle(c.req.raw, {
    prefix: '/rpc',
    context: {
      // user: c.get('user'),
      // env: c.env,
    },
  })

  if (matched) {
    return response
  }

  await next()
})

export default app

client:创建 oRPC client

// rpc/client.ts
import { createORPCClient } from '@orpc/client'
import { RPCLink } from '@orpc/client/fetch'
import type { RouterClient } from '@orpc/server'
import type { AppRouter } from './router'

const link = new RPCLink({
  url: '/rpc',
})

export const client: RouterClient<AppRouter> = createORPCClient(link)

client:页面调用

// app/routes/posts/new.tsx
import { useState } from 'react'
import { client } from '@/rpc/client'

export default function NewPostPage() {
  const [title, setTitle] = useState('')
  const [content, setContent] = useState('')

  async function onSubmit(e: React.FormEvent) {
    e.preventDefault()

    const post = await client.posts.create({
      title,
      content,
    })

    console.log('created post:', post)
  }

  return (
    <form onSubmit={onSubmit}>
      <input
        value={title}
        onChange={(e) => setTitle(e.target.value)}
        placeholder="标题"
      />

      <textarea
        value={content}
        onChange={(e) => setContent(e.target.value)}
        placeholder="内容"
      />

      <button type="submit">创建文章</button>
    </form>
  )
}

适合场景

Web + 小程序 + App
Tauri 桌面端
AI Agent 调用
开放 API
长期业务系统
需要 API 文档
需要统一错误格式

优点是:

前端调用像本地函数
输入输出类型自动对齐
API 语义清晰
未来更适合多端

缺点是:

多一层抽象
早期小项目可能显得复杂

oRPC 的客户端文档也展示了 createORPCClientRPCLinkRouterClient 这类模式,核心就是把服务端 router 暴露成类型安全客户端。(GitHub)


八、三种代码写法对比总结

类型TanStack Server FunctionHono APIHono + oRPC
前端调用体验createPost({ data })fetch('/api/posts')client.posts.create()
后端组织方式函数HTTP 路由业务 procedure
样板代码
类型安全弱到中
多端复用一般
API 文档
适合新手最简单
适合长期架构最强
最适合快速页面逻辑HTTP/API/Webhook多端核心业务 API

九、我们最终的推荐架构

推荐分层

apps/web
  TanStack Start
  TanStack Router
  React
  Tailwind / shadcn

server
  Hono
  middleware
  auth
  webhook
  api boundary

rpc
  oRPC router
  schemas
  procedures
  client

db
  Drizzle
  D1 / Postgres

请求流

Web 页面

TanStack Start

页面私有逻辑:Server Function

多端业务逻辑:oRPC Client

Hono /rpc/*

oRPC Router

Drizzle / D1 / Postgres

边界规则

需求放哪里
页面内部提交表单TanStack Server Function
Dashboard 私有操作TanStack Server Function
支付回调Hono API
微信小程序登录Hono API / oRPC
AI 模型代理Hono API
核心业务 APIoRPC
未来给 App / 小程序 / Agent 复用oRPC
对外开放接口oRPC + OpenAPI
静态内容 / 文档 / SEONext.js / Fumadocs / Astro

十、最终结论

我们不是简单地说:

TanStack 比 Next.js 好
Hono 比 Next.js 好
oRPC 比 Hono 好

而是说:

对于多端应用、Cloudflare-first 部署、快速 MVP、长期 API 复用这个目标,TanStack + Hono + oRPC 的组合比传统 Next.js 更适合。

最终选择可以这样定:

项目阶段技术选择
快速 MVPTanStack Start + Server Functions
有后端接口TanStack Start + Hono
多端产品TanStack Start + Hono + oRPC
内容站/文档站Next.js / Fumadocs / Astro
标准 SaaS / Vercel-firstNext.js
Cloudflare-first 长期模板TanStack Start + Hono + oRPC + Drizzle + D1/Postgres

一句话总结:

TanStack Start 让我们更快做 Web,Hono 让我们更稳地跑 API,oRPC 让我们的 API 能服务多端。 这就是我们选择 TanStack + Hono + oRPC,而不是继续默认 Next.js 的原因。