一个 AI 写作工具从 Next.js 迁移到 TanStack Start,并改用 Gemini Batch API 的记录

发布于

下面这版可以直接发到 V2EX 的「分享创造」或「程序员」节点。

**标题**
一个 AI 写作工具从 Next.js 迁移到 TanStack Start ,并改用 Gemini Batch API 的记录

**正文**
最近做了一个 AI 内容生成工具 [WriteGeniuses](https://writegeniuses.com/):
它目前支持两种内容:

- 长篇 Blog Article
- X Thread

和常见的“输入一句话,直接返回整篇文章”不太一样。WriteGeniuses 会先生成结构化 Plan ,再拆成多个 Unit ,分别生成正文。用户可以单独编辑或重新生成某个段落,不需要每次都重跑整篇内容。

项目最初是 Next.js + Convex ,后来前端迁移到了 TanStack Start 。迁移过程中顺便把生成架构也重做了一遍。

目前主要技术栈是:

- TanStack Start / TanStack Router
- React 19
- Convex
- Google Gemini Batch API
- Cloudflare
- Tailwind CSS + shadcn/ui
- Streamdown
- Creem

这次改动比较大的地方主要有这些。

### 1. 从 Next.js 迁移到 TanStack Start
迁移后,文件路由、`beforeLoad` 路由保护和路由参数都由 TanStack Router 管理。

一开始虽然页面看起来能正常打开,但禁用 JavaScript 后只能看到骨架屏。原因是列表数据仍然完全依赖客户端的 Convex `useQuery`,服务端并没有真正拿到内容。

后来把需要首屏展示的数据放进 route loader:

- 服务端读取登录状态
- 服务端查询 Convex
- 通过 `loaderData` 输出到 HTML
- 客户端启动后再由 `useQuery` 接管实时更新

这样即使 JavaScript 还没加载,Content 和 Templates 页面也已经有基本内容,而不是一整页骨架。

### 2. 把实时生成改成 Gemini Batch API
最早的生成流程是在 Convex Action 里直接等待 Gemini 返回。

短请求还好,但一篇文章会经历:

1. 生成 Plan
2. 生成多个 Paragraph 和 FAQ
3. 生成图片建议
4. 保存并组合 Markdown

只要某个模型响应较慢,Action 就会长时间占用 worker 。并发任务一多,还碰到过 `There are no available workers to process the request` 和 600 秒超时。

后来改成了 Batch API:

- 创建文章后提交 Plan batch
- Convex 立即释放 worker
- Gemini 完成后调用静态 Webhook
- Webhook 根据 batch name 找到本地 job
- Plan 完成后继续提交 Unit batch
- Unit 完成后保存正文并更新文章状态

Convex 里单独保存了 batch job 和 batch request 。每条 Unit 请求都有自己的 `requestKey`,所以 Webhook 返回后不需要让模型再输出 `unitId`,直接使用请求记录完成映射。

某个 Unit 失败时,也只重试对应 Unit ,不会重新生成整篇文章。

### 3. Blog 和 X Thread 完全拆开
最初 X Thread 复用了 Blog 的部分 Plan 和 Unit prompt ,结果很不理想。

生成出来的帖子经常像被截断的文章段落:

- 开头缺失
- 结尾停在半句话
- 混入字符数检查和模型自检文本
- 每条内容单独看还行,连起来却没有 Thread 节奏

后来加入 `planKind`,目前支持:

- `blog_article`
- `x_thread`

两种类型现在分别拥有自己的:

- 创建参数
- Template
- Plan schema
- Plan prompt
- Unit prompt
- 内容校验
- 详情页展示
- 导出格式

这样后面继续增加 Reddit Post 或其他内容类型时,不需要继续往 Blog schema 里塞字段。

### 4. 结构化输出踩了不少坑
Gemini 的 JSON 输出并不是设置了 `application/json` 就一定稳定。

实际碰到过:

- JSON 没有闭合
- Batch 显示成功,但单条 request 失败
- Schema 过于复杂,触发 `Constraint is too tall`
- `responseJsonSchema` 和 `responseSchema` 在 Batch 端表现不同
- 模型返回了 JSON ,但字段不满足最终 Zod schema

现在请求端使用 Gemini 原生 `responseSchema`,返回后仍然使用 Zod 做最终校验。Plan 、Blog Unit 、X Unit 和图片建议分别使用独立 schema ,没有为了省代码强行复用。

### 5. Markdown 和 Mermaid
文章详情页使用 Streamdown 渲染 Markdown ,目前支持代码、数学公式和 Mermaid 。

Mermaid 这里也踩了一个小坑:
```mermaid
B -- Requires --> C{Authentication (API Keys)}
```
括号和旧式连线文字有时会触发解析错误。现在提示词要求:

- 所有节点文字使用双引号
- 带文字的连线使用 `-->|Requires|`
- Mermaid code fence 必须完整闭合

对应写法变成:
```mermaid
graph TD
A["Developer Application"] --> B{"Creem API Gateway"}
B -->|Requires| C{"Authentication (API Keys)"}
```

### 6. 目前已经能做什么
现在的版本支持:

- 从结构化 Brief 创建 Blog 或 X Thread
- 公共模板和用户自定义模板
- Serper 搜索结果作为研究参考
- 分段生成和单 Unit 重试
- Markdown 编辑与导出
- Mermaid 图表渲染
- 图片建议和生成提示词
- 文章组管理
- 积分消耗和 Creem 支付
- SSR 首屏数据
- 明暗主题

新注册账号会有一些免费积分,可以直接跑一次 Blog 或 X Thread 。

线上地址: [WriteGeniuses](https://writegeniuses.com/)

目前还是早期版本,我自己最想继续改进的是生成内容的“真人感”,尤其是怎样让用户把真实经验、案例和数据更自然地带进 Brief ,而不是只生成结构正确但比较泛的内容。

如果有 V 友愿意试一下,欢迎反馈创建流程、生成质量,或者你觉得下一个应该支持什么内容类型。

---

原文链接:[点击查看](https://www.v2ex.com/t/1228888)

评论(3)

现在大家又开始拥抱 TanStack 了吗?巧了,我最近也在折腾这套技术栈。

· 0 个赞

回复

主要是 Next.js 感觉和 Vercel 绑得太深了,而且写法太死板不够灵活。另外它自带的打包体验确实不如 Vite 爽。综合比下来看,TanStack 现在基本可以算是上位替代了。

· 0 个赞

不是哥们,你用 AI 帮忙润色排版我能理解,让 AI 帮你写全文我也忍了。但是 AI 给你回复完内容,你自己都不看一眼就直接无脑复制粘贴过来发帖吗?连开头原本 AI 提示你的“下面这版可以直接发到 V2EX”都原封不动带过来了。

· 0 个赞