摘要
本文从一次实验出发:为 Next.js + Zod 4 项目编写 OpenAPI 生成脚本,实验成功但结论为”不必要”。由此展开对全栈 UI 框架(Next.js / Nuxt / SvelteKit / Remix)与 API 框架(FastAPI / Hono / Express)在架构哲学上的对比分析,并探讨 AI 时代 API 契约的最优传递形式。
核心结论:全栈 UI 框架不支持 OpenAPI 是设计选择而非功能缺失;AI 消费者更偏好 Markdown 而非 JSON Schema。
1. 实验背景
1.1 问题
在 Next.js + Drizzle + Zod 4 的全栈 TypeScript 项目中,16 个 feature 模块各自维护 schemas.ts(Zod 定义)和 actions.ts(Server Actions)。项目需要:
- 给外部 AI 工具(V0/Stitch)提供 API 契约以生成 UI
- 在没有前端的情况下独立测试后端逻辑
FastAPI 项目中,这两个需求由自动生成的 openapi.json 同时满足。问题是:Next.js 能否复制这一能力?
1.2 实验过程
| 步骤 | 方案 | 结果 |
|---|---|---|
| 1 | @asteasolutions/zod-to-openapi | 失败 - 不支持 Zod 4 |
| 2 | zod-to-json-schema | 失败 - Zod 4 返回空对象 |
| 3 | Zod 4 原生 z.toJSONSchema() + 手动路由注册 | 成功 - 50 paths, 41 schemas |
最终脚本约 300 行,需要手动维护 import 列表和路由映射。
1.3 实验结论
脚本成功运行,但经评估后决定删除。原因见第 3 节。
2. 框架哲学对比
2.1 两类框架的定位
| 维度 | API 框架(FastAPI/Hono) | 全栈 UI 框架(Next.js/Nuxt) |
|---|---|---|
| 核心抽象 | HTTP 请求/响应 | 页面/组件 |
| 后端角色 | 后端即产品 | 后端是前端的实现细节 |
| 消费者 | 不确定(多端/第三方) | 确定(自身前端) |
| OpenAPI | 自动生成(零配置) | 不支持 |
| 典型调用 | fetch("/api/users") | createUser(data) |
2.2 Server Actions 为什么没有 URL
Next.js Server Actions 的设计刻意移除了 HTTP 层:
- 无 URL - 函数调用,不走路由
- 无 HTTP 方法 - 不区分 GET/POST/PATCH
- 无状态码 - 不返回 201/404/500
- 无序列化协商 - 不需要 Content-Type
这消除了前端调后端时的全部 HTTP 开销。当消费者就在同一个项目中时,HTTP 协议的所有契约机制(URL 设计、状态码语义、Content Negotiation)提供零价值。
2.3 全行业的共识
| 框架 | 生态 | 后端机制 | 耦合度 | OpenAPI |
|---|---|---|---|---|
| Next.js | React | Server Actions | 最高 | 无 |
| Nuxt.js | Vue | server/api/ + Nitro | 中 | 无 |
| SvelteKit | Svelte | Form Actions | 高 | 无 |
| Remix | React | Loaders/Actions | 高 | 无 |
| SolidStart | Solid | Server Functions | 高 | 无 |
所有主流全栈 UI 框架均不支持 OpenAPI 自动生成。这不是巧合,而是同一设计原则的不同实现:当前端是唯一消费者时,HTTP 是不必要的抽象层。
2.4 两个阵营的适用场景
- 全栈 UI 框架:优化 99% 的场景(前端即唯一消费者)
- API 框架:优化 1% 的场景(多消费者/对外开放)
99% 的 Web 项目一辈子不需要对外 API。框架为多数场景优化是合理的工程选择。
3. OpenAPI 在 Next.js 中的成本分析
3.1 FastAPI 的零成本模型
@app.post("/api/users")
def create_user(data: UserCreate) -> User:
...
# openapi.json 自动包含这条路由,零额外代码
OpenAPI 有价值,因为它是路由定义的副产品,维护成本为零。
3.2 Next.js 的高成本模型
// 脚本中手写:
addCrud("/api/users", "Users", "User", "UserCreate", "UserUpdate");
addPath("post", "/api/users/me/change-password", "Users", "Change password", { body: "ChangePassword" });
这些路由在项目中不存在。每新增 feature 需要手动更新脚本。这不是自动化,而是新增了一份需要同步的代码。
3.3 成本对比
| 维度 | FastAPI | Next.js (脚本) |
|---|---|---|
| 路由映射 | 自动 | 手动(每个 feature) |
| Schema 转换 | 自动 | 需要注册 |
| 新增 feature | 零额外工作 | 更新脚本 |
| 路由真实性 | 真实端点 | 虚构 URL |
| 维护成本 | 零 | 持续 |
结论:在 Next.js 中生成 openapi.json 的成本远高于收益。
4. AI 时代的契约传递
4.1 消费者视角验证
本次实验的关键转折点是直接询问 AI(openapi.json 的目标消费者):“你需要这个格式吗?”
AI 的评估:
- Markdown 表格是扁平结构,逐行扫描即可理解
- openapi.json 需要追踪
$ref引用、解析嵌套的allOf/oneOf - 55KB JSON 对 context window 不友好;等价的 Markdown 约 2-3KB
4.2 推荐的契约格式
对 V0/Stitch 等外部 AI 工具,推荐 Markdown 表格:
## Users
| Field | Type | Required | Constraints |
|-------|------|----------|-------------|
| username | string | Y | 1-150 chars |
| email | string | Y | email format |
| password | string | Y | 8-128 chars |
生成方式:AI 扫描 src/features/*/schemas.ts,无需维护脚本。
4.3 完整的 UI 生成输入
仅有 API 契约不足以生成高质量 UI。完整输入应包括:
| 文档 | 内容 | 来源 |
|---|---|---|
| UI.md | 页面布局、交互、列定义 | 人工编写 |
| API.md | 数据结构、操作、约束 | AI 从 schemas.ts 生成 |
| Mock 数据 | 示例 JSON | AI 生成 |
UI.md 告诉 AI “画什么”,API.md 告诉 AI “数据长什么样”。
5. 对外 API 的预留方案
当项目未来需要对外 API(移动端/第三方集成)时,推荐方案是在 Next.js 中嵌入 Hono:
// app/api/[...route]/route.ts
const app = new Hono()
.post("/users", async (c) => {
const result = await createUser(await c.req.json());
return c.json(result, 201);
});
Hono 的 @hono/zod-openapi 可复用已有 Zod schema 自动生成 openapi.json。此时路由是真实的,OpenAPI 才有意义。
关键原则:需要时再加,不提前支付维护成本。
6. 结论
- 全栈 UI 框架不支持 OpenAPI 是设计选择,不是功能缺失。当前端是唯一消费者时,HTTP 契约机制无价值。
- 在 Next.js 中强行生成 openapi.json 成本高于收益:路由是虚构的,脚本需要持续维护。
- AI 消费者更偏好 Markdown 表格而非 JSON Schema:更紧凑、更易解析、更省 token。
- 对外 API 需求出现时,Hono 嵌入方案可在半天内完成,无需提前建设。