设计理念与技术选型
理解 01MVP 模板为什么使用 TanStack Start、monorepo、共享包和 SSR,以及以后扩展到移动端时应该复用哪些部分
01MVP 模板的目标很明确:让一个人或小团队能快速做出可以上线、可以收费、可以继续维护的产品。
它不追求把所有技术都放进来。这里的选择围绕四件事展开:
- 启动快:账号、数据库、支付、AI、文档、部署都有默认路径。
- 边界清楚:页面、业务能力、接口、数据库、共享组件各有位置。
- 后续能长大:从单个 Web 产品开始,保留 API、数据库和共享包的扩展空间。
- AI 能协作:仓库规则、文档结构和 package 边界足够明确,AI 编码工具不需要猜。
为什么用 TanStack Start
这套模板选择 TanStack Start,是因为它同时满足几个早期产品常见需求:
- React 生态完整,适合快速搭建产品界面。
- Vite 构建速度快,本地开发体验好。
- 文件路由清楚,页面和布局容易定位。
- SSR 能让首页、文档、定价页、案例页更适合搜索引擎和分享链接。
- 和 TanStack Query 配合自然,服务端预取和客户端缓存可以使用同一套数据模型。
TanStack Start 的一个重要特点是:很多代码默认会在服务端和客户端都运行。它的 loader 也不是传统意义上的服务端专用函数。需要访问数据库、密钥、支付接口、私有 API 时,应该放到服务端函数、Hono/oRPC 接口或后端 package 里。
为什么保留 SSR
如果产品只有登录后的后台,纯客户端应用也能完成很多需求。但 01MVP 面向的是“要发布、要介绍、要获客、要收费”的产品,通常会有这些页面:
- 首页
- 定价页
- 文档
- 案例
- 支付说明
- SEO 页面
- 分享落地页
这些页面适合由服务端先渲染出稳定 HTML。用户打开更快,搜索引擎和社交平台也更容易读取页面内容。
SSR 带来的代价是前端组件要更守规矩:服务端渲染出的 HTML 必须和客户端接管时的第一帧一致。主题、浏览器语言、localStorage、屏幕宽度、时区、随机数这些浏览器状态,不能直接决定服务端输出的 JSX。
模板里的做法是:
- 首屏关键数据用服务端可见的来源,例如 cookie、请求头、loader 预取或 React Query 脱水数据。
- 浏览器偏好先渲染稳定默认值,hydration 后再增强。
- 完全依赖浏览器 API 的组件放到 client-only 边界里。
- 文档、首页、定价页这类公开页面尽量写成 SSR-safe 组件。
以后做 APP 怎么理解
Web SSR 页面不会直接变成原生 APP 或小程序页面。DOM、CSS、shadcn/ui、TanStack Router 的页面结构,都属于 Web 客户端。
真正应该复用的是这些底层能力:
- 数据库 schema 和迁移
- 认证模型和用户体系
- oRPC/Hono 暴露出来的业务接口
- 输入校验、权限判断、订单、积分、AI 调用等业务规则
packages/*里的共享类型和服务逻辑
所以这套模板的长期边界是:
| 层级 | 当前用途 | 未来 APP 是否复用 |
|---|---|---|
products/01mvp/apps/web | Web 页面、SSR、文档、控制台 | 主要复用接口调用方式和产品逻辑,UI 通常重写 |
products/01mvp/packages/api | 业务接口、oRPC 路由、服务编排 | 高度复用 |
products/01mvp/packages/db | schema、迁移、数据库访问 | 高度复用 |
products/01mvp/packages/auth | 登录、会话、用户能力 | 高度复用 |
products/01mvp/packages/config | 01MVP 环境默认值和产品配置入口 | 高度复用 |
packages/ui | Web UI 原语 | 只在 Web 复用 |
packages/config | 共享环境变量 schema | 部分复用 |
如果未来要做移动端、小程序或桌面端,建议新建一个客户端应用,让它调用同一套 API 和业务 package。不要把 Web 页面组件当成跨端复用层。
为什么用 monorepo 和 products 结构
一个早期产品通常会同时有 Web 应用、API、认证、数据库、UI、日志、AI、支付、存储等模块。一个长期做 Web coding 的仓库还会继续长出多个产品、多个客户端、内部工具、Agent Skills 和一套自己的工程规范。把它们放进一个仓库,能减少很多同步成本:
- 改 schema 时,API 和页面能在同一次提交里同步调整。
- 共享类型不需要发布 npm 包才能使用。
- 统一 lint、type-check、build 和测试命令。
- AI 编码工具能一次看到完整上下文。
- 后续拆服务时,有明确 package 边界可以参考。
products/<product> 这一层用来隔离具体产品。01MVP、OneSay 或未来的新项目可以共享根目录的工具链、规范和通用包,但各自保留自己的 apps、API、DB、auth、config 和业务逻辑。这样不用在多个仓库之间复制基础设施,也不会让一个产品的代码默认渗透到另一个产品。
monorepo 的代价是目录会比单应用项目多一些。所以模板要求 route 文件保持薄,业务能力沉到 products/<product>/packages、根共享 packages 或 slice-local 模块里。
结构设计原则
route 只负责入口
products/01mvp/apps/web/src/routes 负责 URL、布局、鉴权、head()、loader 和页面挂载。不要把复杂业务逻辑写进 route 文件。
页面 UI 放到 products/01mvp/apps/web/src/pages,可复用业务组件放到 products/01mvp/apps/web/src/features,布局和组合型区域放到 products/01mvp/apps/web/src/widgets。
API 是跨端边界
Web 可以直接使用 TanStack Start 和 oRPC,但业务能力不应该被锁在页面组件里。新增产品能力时,优先思考它是不是应该有清晰的 API contract。
未来如果做 APP、小程序、开放接口或后台任务,这个边界会决定迁移成本。
packages 存放可复用能力
packages 不是杂物间。只有多个产品会复用、或者需要和 Web 客户端解耦的能力,才适合放进根目录 packages/*。只属于某个产品、但会被该产品多个 app/package 复用的能力,放进 products/<product>/packages/*。
典型例子:
products/01mvp/packages/api:业务接口和服务编排products/01mvp/packages/config:01MVP 产品默认值和 env 入口products/01mvp/packages/db:数据库 schema 和迁移products/01mvp/packages/auth:认证和用户能力products/01mvp/packages/i18n:消息和语言运行时packages/ui:跨产品 Web UI 原语packages/email:跨产品邮件发送与模板基础设施packages/logger:结构化日志
单个页面自己的状态、弹窗、表单逻辑,优先留在对应页面或 feature 目录。
技术栈选择
| 模块 | 选择 | 原因 |
|---|---|---|
| Web 框架 | TanStack Start | React + Vite + SSR,适合产品站、文档和登录后应用放在同一套工程里 |
| 路由 | TanStack Router | 类型安全路由,文件结构清楚,适合复杂布局和受保护页面 |
| 数据请求 | TanStack Query + oRPC | 客户端缓存、服务端预取、类型安全接口可以连在一起 |
| HTTP 服务 | Hono | 轻量,适合 API、Webhook、健康检查和特殊 HTTP 路由 |
| 数据库 | PostgreSQL + Drizzle | schema、迁移和类型在 TypeScript 项目里更容易维护 |
| 认证 | Better Auth | 覆盖邮箱、手机号、OAuth、Magic Link 等常见登录方式 |
| UI | Tailwind CSS v4 + shadcn/ui | 组件可控,主题可改,适合做产品后台和文档站 |
| 国际化 | Paraglide | 翻译 key 明确,适合和路由、构建、AI 翻译工作流配合 |
| 文档 | Fumadocs | MDX 文档、侧边栏、搜索和多语言文档比较完整 |
| 工作区 | Vite Plus + pnpm workspace | 统一 monorepo 命令、依赖和检查流程 |
| 部署 | Zeabur + Cloudflare,Cloudflare Workers 可选 | 默认部署路径低门槛,同时保留边缘部署空间 |
做功能时的判断顺序
新增功能前,可以按这个顺序判断放在哪里:
- 它是否只是一个页面展示?放到
products/01mvp/apps/web/src/pages。 - 它是否是页面之间复用的 Web 功能?放到
products/01mvp/apps/web/src/features或widgets。 - 它是否只属于 01MVP 但会被 01MVP 的 API、后台任务、未来 APP 复用?放到
products/01mvp/packages/*。 - 它是否会被多个产品复用?放到根目录
packages/*,保持产品无关。 - 它是否访问数据库、密钥、支付、私有服务?放到服务端边界里。
- 它是否依赖浏览器状态?让服务端和 hydration 第一帧先渲染稳定结果,再在客户端增强。
这个判断顺序能降低两个长期风险:Web 页面越来越难迁移,SSR 页面越来越容易出现 hydration 问题。
推荐阅读
这篇文档有问题?