颜力刚Ligang Yan

CurioSeed V4:技术选型与团队协作实践

把做 CurioSeed V4 的几个关键决定摊开来讲:Supabase 还是 Neon,React SPA 还是 Next.js,前后端分离还是 SSR,什么时候拆 monorepo,以及一套人加 AI 加规则护栏的协作方式。每一项都附踩过的坑。

engineeringnextjsneonclaude-codeteam

English version: CurioSeed V4: stack choices and how a small team works with AI

这不是炫技。我想把过去几个月做 CurioSeed V4 的几个关键决定摊开来讲:为什么这么选、代价是什么、规模化怎么走、人和 AI 怎么协作。每一项都会附上踩过的坑,不藏。

读完你会带走三样东西:

  1. 我们当前的技术架构是怎样的,哪些地方是有意为之的边界;
  2. 三个关键选型(数据库、框架、取数)的真实取舍;
  3. 一套可复用的“人 + AI 协作 + 规则护栏”工作方式。

1. 当前项目的技术架构

产品定位:面向 5 到 12 岁儿童的“教 AI 宠物变聪明”育成游戏。教学、测验、知识星系、商店养成连成一个闭环。

请求链路(自上而下)

浏览器(Next.js App Router,"use client" 页面 + React Query)
        │  fetch /api/*

Route Handlers(src/app/api/**)
   · getSessionUser 鉴权       → 401
   · zod 校验入参              → 400
   · safeRoute 统一错误        → 500


server-only 数据层(src/lib/neon.ts)   ← 注入会话 JWT


Neon Data API(PostgREST)+ RLS / 原子 RPC
─────────────────────────────────────────────
认证:Neon Auth(Better Auth,cookie session)  ← src/proxy.ts 路由保护
部署:Vercel(iad1,紧贴 Neon us-east-1)

技术栈一览

维度 选型
框架 Next.js 16(App Router)· React 19 · TypeScript 5
样式 Tailwind v4 · Framer Motion
数据 Neon Postgres + Data API · Neon Auth
客户端 React Query · zod
测试 Vitest + Playwright
部署 Vercel(iad1,与 Neon 同区域)

三条架构边界

  • 客户端只碰 /api/*,不直接访问数据库。
  • 数据库访问只能在 server-only 数据层,组件里禁止 import neon.ts。
  • 认证在 proxy.ts 兜底,未登录请求在路由层就被拦下。

2. 关键技术选型

2.1 数据库:Supabase 还是 Neon

维度 Supabase Neon(我们的选择)
Postgres
自动数据 API PostgREST Data API(PostgREST 兼容)
认证 Supabase Auth Neon Auth(Better Auth,身份存自己库)
杀手锏 生态成熟 数据库分支:写时复制克隆,秒开

为什么选 Neon

  • Serverless 加数据库分支:每个 PR 能克隆一个临时库跑迁移和测试,用完即弃。这条对我们价值最大。
  • Neon Auth 加 Data API 补齐了 Supabase 的同款能力.from().select()、RLS、auth.user_id(),迁移心智几乎一比一。
  • 与 Vercel 同区域、Serverless 亲和,延迟和扩缩容都顺。

劣势,诚实说

  • @neondatabase/authpostgrest-js 还是 beta(0.x),文档少。好几处只能靠读类型定义才搞清 API。
  • Data API 的 schema 缓存不会自动刷新:建完表必须手动点 “Refresh schema cache”,否则报 PGRST205。我们踩过。
  • 打包进来的 better-auth 有一个 high 级别漏洞,等上游修。

怎么控风险

  • pin 精确版本,Dependabot 盯更新;
  • 迁移在 CI 的 Neon 临时分支上跑回归;
  • 把“建表后刷 schema”写进 rules,让 AI 帮你记。

2.2 框架:React SPA 还是 Next.js

选择:Next.js 16(App Router)。

优势

  • 一体化:路由、SSR、Route Handlers(后端)、proxy(中间件)、打包,一套搞定。
  • 前后端同仓、同语言、同类型src/lib/types.ts 共用),少一层胶水。
  • Vercel 零配置部署;Server 和 Client 组件按需选。

劣势

  • Next 16 破坏性变更多,且新于训练数据:middleware 改成 proxy、缓存语义都变了。人和 AI 都容易套旧 API。 对冲办法:AGENTS.md 明确警示“先读 node_modules/next/dist/docs/ 再写”。
  • App Router 的心智负担:Server 和 Client 的边界、server-only 标记、“use client” 的传染。

对比纯 React SPA:省了自己搭路由、SSR、API、构建,代价是绑定 Next 的约定。对“要快速出全栈产品的小团队”,这笔账划算。

2.3 取数:前后端分离还是 SSR

这是我们真实纠结并切换过的决策。

最终选择:前后端分离。客户端 React Query hooks → /api/* Route Handlers → 数据层。

为什么不直接用 Server Components 取数

  • 项目强交互,页面大多是 “use client”
  • Neon Auth 是 cookie 加服务端 session,客户端自管 token 跟它混用会踩坑。
  • 想要清晰边界(server-only 的 neon.ts 绝不进客户端),并为将来**多客户端(App)**留 REST 契约。

优势:边界清晰好测(mock /api 即可);客户端缓存、重试、失效统一交给 React Query;数据库访问只在服务端,更安全。

劣势:比纯 SSR 多一层样板(每个资源都要 endpoint 加 hook);首屏非直出,有 loading 态;需要纪律,组件别裸 fetch、别 import neon.ts。

一个真实踩坑:并发查询里 auth.token() 出现竞争,部分请求丢 token、被 RLS 挡空。修法是用 React cache() 做请求级去重,token 拿一次复用。

折中:用聚合端点/api/me 一次返回 profile、pet、quests、test)减少往返。纯展示或 SEO 页未来仍可用 Server Components。

3. 规模化演进:pnpm monorepo

现状:单 Next 应用,前后端同仓,Route Handlers 当后端。MVP 阶段最快。

用户量上来后,用 pnpm workspace 拆成 monorepo:

apps/
  web/        Next.js 前端
  api/        独立后端服务(把重逻辑从 Route Handlers 拆出)
  worker/     后台任务:AI 对话、统计聚合、定时任务
packages/
  ui/         共享组件
  types/      共享领域类型(现在的 src/lib/types.ts)
  config/     eslint / tsconfig / tailwind 预设
  core/       业务纯逻辑(quizBank / shopCatalog / rng …)

为什么是 pnpm:硬链接省磁盘,严格依赖防 phantom deps,workspace 协议,比 npm 和 yarn 快。配 Turborepo 做任务编排和缓存。

什么时候拆:后端变重(LLM 对话、实时、定时任务)需要独立伸缩和部署时;团队变大、按领域拆包各自有 owner 时。现在不过度设计。 单仓够用就单仓,拆分是用户量和团队规模触发的演进,不是一开始就上。

4. 团队协作:Git 工作流加 Issue 管理

Issue 管理需求和 Bug:需求、Bug、技术债都走 GitHub Issue,分级 high / medium / low,标来源,附修复建议。AI review 的发现也 triage 进 issue。

分支加 PR,不走 fork:内部团队直接在主仓切分支。铁律是永远基于最新的 origin/main:

git fetch
git checkout -b <type>/<topic> origin/main

命名 feat/*fix/*chore/*,commit 用 conventional 风格。人人都有 PR 权限,CI 绿加测试通过就可以合。PR 合并等于分支生命周期结束,后续改动走新分支新 PR。

质量闸门(CI)

关卡 内容
Lint 硬卡,不通过禁止合并
next build 类型检查
npm test Vitest 全量
DB 分支回归 每个 PR 在 Neon 临时分支上跑迁移加断言

Code Review:人加 AI 自动 review(Gemini)。真问题进 issue,误报标注。

5. AI 协作:Codex / Claude 加 CLAUDE.md 和 rules

协作模式

人(定大纲、决策)
  → AI(Claude / Codex)  实现、查文档、写测试、开 PR
  → AI(Gemini)          review
  → 人                    拍板合并

三层 markdown 治理,工具无关、纯文本:

文件 作用
CLAUDE.md(仓库根) 项目总纲:产品、栈、命令、结构、现状、全局约定、Git 铁律。AI 进来先读。
AGENTS.md 对 AI 的硬警示,比如“Next 16 有破坏性变更,先读官方 docs,别套旧 API”。
.claude/rules/* 按目录自动加载的细则(nextjs、styling、neon、testing)。编辑哪块就自动载入哪份规则。

规则即护栏,把踩过的坑固化成约定。举几个例子:

  • 客户端绝不 import server-only 的 neon.ts;
  • 新 RPC 一律 SECURITY DEFINER;
  • 装饰性随机用 seeded(),别用 Math.random
  • 改完 DDL 必须刷 Data API 缓存。

每踩一个坑,写回 rules,人和 AI 都不再重复踩。 核心价值是“经验沉淀为可执行的上下文”。Codex 和 Claude 都能读同一套约定,换工具不丢知识。

6. 测试体系

三层测试加 CI 闸门

工具 测什么
单元 Vitest 纯函数(quizBank、shopCatalog)
组件 Vitest + RTL props 到渲染(jsdom)
集成 Vitest(node) Route Handler:mock neon 和 guard,断言 401 / 400 / 500 和响应结构,不连真实数据库
E2E Playwright smoke:未登录跳转、sign-in 渲染、/api 返回 401(不依赖真实 Neon)
DB 回归 CI + Neon 分支 每个 PR 克隆临时库,跑迁移加结构化断言(表、RLS、函数、种子)

关键约定:server-only 在 Vitest 里 alias 成空,服务端模块可被测;真实 RLS 和认证的端到端留作“需要 creds 的门控 e2e”,不进默认 CI;lint、build、test 必过才能合 main。

7. 总结与下一步

三条哲学

  1. 选型:用“能补齐能力的新基建(Neon)“换开发体验与演进空间;用”pin 加迁移 CI 加 Dependabot 加规则护栏“控 beta 风险。
  2. 架构:前后端分离,server-only 边界,错误不吞,入参校验。先把地基(稳定、可观测、测试)打牢,再堆功能。
  3. 协作:轻量 Git(分支加 PR,人人可合,测试把关)乘以 AI 护栏(CLAUDE.md 和 rules 把经验固化)。

下一步:功能上接 LLM 做 AI 宠物对话、家长页真实统计;加固上做原子结算、限流、接 Sentry;规模化上按触发点拆 pnpm monorepo。

English version.