# 技术选型与落地

本参考给出新 TypeScript 全栈项目的推荐基线。它不是要求把所有技术一次装齐的清单：只有技术确实让当前用户任务、可靠性或交付更简单时才采用。版本以新项目自己的锁文件和 Node LTS 兼容性为准，不复制参考项目的版本、镜像、地址或环境取值。

## 选择原则

先确定首条完整用户链路，再选为该链路减少复杂度的技术。需要账号、数据或业务副作用的产品，Web 界面、API、业务服务、数据库和必要的真实开发数据源必须一起落地。任何框架都不能替代这一链路；前端 mock、静态卡片和假 API 响应只可用于明确标识的短期演示。

| 问题 | 优先选择 | 采用条件与边界 |
| --- | --- | --- |
| Web 与服务端应用 | Next.js App Router + TypeScript | 默认保持一个全栈模块化单体。Server Component 用于服务端读取和首屏组合；Client Component 只覆盖真实交互边界。不要为每个页面机械添加客户端状态或 API 层。 |
| 小型项目的管理后台 | 同一 Next.js 应用中的受保护路由 | 管理页面与主产品共用域名、认证和业务服务；可使用 `/admin` 等清楚入口。每个后台读取和写入都在服务端检查权限，不能靠隐藏菜单。没有独立组织、部署或安全边界时，不建第二个后台项目、子域或 BFF。 |
| 界面基础与组件 | Tailwind CSS + shadcn/ui | 使用统一 token 和少量语义样式；按当前界面需要引入组件，组件代码归项目所有并可直接调整，不预装完整组件集或复制多套主题。 |
| 界面绑定的服务端操作 | Server Action 调用业务服务 | 适合只由当前 Next.js 界面触发的 mutation；Server Action 负责输入与会话适配，不承载可复用业务规则。 |
| 对外或多客户端 HTTP API | Next.js Route Handler；边界成熟后可独立 API | 路由只负责可信身份、协议和响应，调用同一业务服务。只有独立部署、非 Web 客户端、WebSocket 或运行时约束成为事实时才拆出 Hono 等独立 API。 |
| 关系数据与在线迁移 | PostgreSQL + Drizzle 或 Prisma | ORM 二选一；Schema、迁移和连接放在数据库边界。提交生成迁移并审阅实际 SQL，不改写已应用迁移。 |
| 本地与生产封装 | Docker | 本地优先容器化 PostgreSQL 等有状态依赖，应用可直接用 pnpm 运行；生产构建不可变应用镜像。固定外部镜像版本或 digest，秘密配置不得进入构建上下文或镜像层。 |
| 跨实例缓存、限流、锁、队列或 Pub/Sub | Redis | 只有单进程内存或 PostgreSQL 不能正确、简单地满足已确认需求时引入。先写明数据归属、TTL／失效、重试与降级；未使用时不启动空 Redis 服务。 |
| 浏览器存在复杂的客户端服务端状态 | TanStack Query | 仅当客户端频繁刷新、乐观更新或跨组件复用远端状态带来实际收益时使用；简单读取优先使用 Next.js 的服务端数据流。 |
| 多应用确需共享契约或纯逻辑 | pnpm workspace + 边界清晰的共享包 | 只有出现真实的第二个应用或独立包消费者时才建立；请求／响应 schema、仅编译期类型和无状态纯函数分开，不因目录整齐而拆包。 |
| 确有可靠后台工作 | PostgreSQL 队列或 Redis 队列，按现有基础设施选择 | 仅当任务需要跨请求持续执行、调度或可靠重试时引入；先复用项目已有的可靠存储，不为一个简单异步动作同时增加 Redis 和专用 Worker 平台。 |
| 明确要求模型对话与受控工具 | AI SDK + 服务端 OpenAI 兼容适配器 | 只为 Agent 产品建立一条模型工具循环；不在同一产品叠加 Mastra 或第二个运行时，除非已有实现无法满足明确需求。 |
| 明确需要成熟聊天展示 | assistant-ui ExternalStoreRuntime | 只替换对话视图与输入体验；会话、草稿、发送锁、私有附件、工具执行和持久化仍由应用控制。 |
| 用户上传或运行时文件／图片 | OSS／S3 兼容对象存储 | 生产环境不写 Git 或应用本地目录。服务端先鉴权再签名、读取或处理；数据库保存对象键与归属等元数据，不把文件 URL 当作权限。 |
| 公网入口与低中断发布 | 反向代理 HTTPS + 同源 `/api` + 按需蓝绿候选 | 应用端口仅对私有网络开放；蓝绿仅在低中断切换有实际收益时采用。 |

## Drizzle 与 Prisma 的选择

二者都能与 PostgreSQL 组成可靠的数据层，选择目标是让团队只维护一套 Schema、迁移和查询心智模型。

| 选择 | 更适合的情况 | 需要守住的边界 |
| --- | --- | --- |
| Drizzle（默认优先） | 团队熟悉 SQL，希望类型安全同时保持查询与迁移透明，或需要精确控制 PostgreSQL 能力。 | 生成迁移后审阅 SQL；复杂查询留在数据库边界，不把 ORM 表结构直接当成业务 API。 |
| Prisma | 团队更看重声明式 Schema、生成 Client、Studio、成熟生态与快速建模，且其迁移和查询能力满足当前需求。 | 同样审阅生成 SQL；不要用 Client 便捷性绕过业务服务、授权、事务或在线迁移规则。 |

新项目默认选其中一个并贯彻到底。仅在正在执行有明确退出条件的 ORM 迁移时，才允许两者短期共存。

## 前端与后端的真实边界

浏览器只提交用户输入、受校验的对象引用、请求标识和明确许可；不接受客户端提供的用户 ID、历史正文、账号归属、工具结果、费用或完成证据。服务端是身份、会话、消息、工具执行和业务记录的权威来源。

按知识归属组织服务。例如计划、文件、账号或练习各自提供少量清晰操作，内部封装访问检查、锁、版本、事务和失败清理。HTTP API、Worker 与 Agent Tool 调用同一服务，不能由每个入口拼装“先查 owner、再加锁、再写入”的固定顺序。

Next.js 客户端应直接试用受影响的交互。保存、上传、操作卡片或对话中的动作必须通过 Server Action 或 Route Handler 到达真实业务服务，并以服务端结果渲染成功、冲突或错误。不要先搭“组件市场”“状态管理层”“性能中心”“统一缓存平台”再寻找用途。

## 仅在对话产品中的取舍

只有用户明确要求复杂、成熟的聊天输入和消息展示时，才可以采用 assistant-ui 的 ExternalStoreRuntime；它不是服务端运行时，也不应执行客户端 Tool。保留应用自身对下列事实的控制：

- 服务端会话历史、用户草稿、附件上传和发送锁；
- 提交快照、失败后的草稿恢复、真实网络取消和页面导航连续性；
- 私有附件、业务结果和工具活动的自定义展示；
- 输入法组合、只有附件的发送、键盘行为与焦点恢复。

升级此类聊天依赖时，验证输入法、取消、失败恢复、私有附件、工具结果、刷新后历史一致性和窄屏布局。包体积警告是测量信号而非自动拆包授权；先确认用户可感知的加载或交互问题。

## 数据、异步和性能

写操作使用请求 ID 或由服务端生成的稳定幂等键；唯一约束、事务和版本号表达可验证的不变量。重复提交、并发编辑、取消和响应丢失时，返回明确结果而不重复制造业务副作用。

跨数据库与文件存储的操作没有天然原子事务。数据库提交不确定时，先确认是否已有持久引用，再决定是否清理文件；无法确认时保留对象和诊断，不能以删除用户数据来“回滚”。

性能优化以证据开始：统计重复请求、查询范围、首屏或交互等待，以及按需存在的模型上下文规模和成本。优先移除重复请求、全量读取和无关上下文；缓存、并行、索引、队列或拆服务必须说明它解决的已测问题、数据归属和失效方式。不要预留通用性能模块。

## 需要单独决策的技术

以下技术可能适合特定产品，但不得因为参考项目存在就默认创建：短信身份服务、对象存储、语音／图片模型、公开联网检索、向量检索、长期记忆、任务续接、SEO 公开页面、蓝绿槽、Worker、微服务和第三方插件执行。其中对象存储在没有图片／文件功能时不创建；产品一旦包含用户上传或运行时文件，它就是替代 Git 和应用本地目录的必要持久化边界。

每项被选中时，在对应领域文档写明：当前解决的问题、服务端所有权和数据边界、环境配置来源、失败／重试／成本语义、实际验证和未验收部分。短信身份服务的具体做法见[新通短信登录](../extensions/xintong-sms-identity.md)；需要 Agent 时见[单 Agent 与受控工具](../extensions/agent-runtime.md)。

产品进一步需要同源 BFF、公开 Agent API、跨端能力定义或可观测性时，补充阅读[IELTS Buddy 的扩展工程经验](../extensions/ieltsbuddy-development-experience.md)。
