简介
ShipKit 是什么、模板里带了哪些功能、基于什么技术栈以及为什么这样选,还有仓库的目录结构。
ShipKit 是一个 SaaS 起步模板:一个已经能跑起来的完整应用,带登录、支付、积分、管理后台、邮件、多语言和营销站,跑在 TanStack Start 和 Cloudflare Workers 上。你把它克隆下来、改个名字、删掉用不到的部分,然后在剩下的空间里做自己的产品。
整个模板围绕一条原则来写:每个可选功能都必须能在几分钟内删掉。每个模块都在自己的目录里,只在做了标记的位置改动共享文件,并且自带一份删除配方,CI 会在每个 pull request上跑一遍,证明删掉它之后应用照样能构建。第二个设计目标是让编程 agent 能在这个仓库里干活而不把它搞坏:约定写在 CLAUDE.md 和 AGENTS.md 里,其中重要的几条由测试强制执行,而不是靠自觉。
模板里有什么
| 模块 | 内容 |
|---|---|
| 登录 | 基于 better-auth 的无密码登录:Google(含 One Tap)、配置后可用的 GitHub,以及发到任意邮箱的六位验证码。没有密码,也就没有重置密码流程 |
| 支付 | Stripe 和 Creem 共用一层与服务商无关的事件层:订阅、一次性积分包、结账、客户门户、退款。默认用 Stripe |
| 积分 | 一本积分账:按套餐每月发放、可购买的积分包、不会透支的扣减,以及管理员手动调整 |
| 管理后台 | /admin,分五个分区:概览(KPI 和图表)、用户(用户、候补名单、反馈)、收入(订阅、订单、积分、推广)、开发者(API key、审计日志、邮件日志)和设置(运营设置、角色、邮件预览) |
| 权限 | 基于角色的访问控制,支持通配符授权。每个功能自己声明权限,同一条授权同时守住页面、server function 和 API |
| API | 只读 JSON API,配两种 API key:/api/v1/admin/* 供你自己做分析,/api/v1/me/* 给客户读取自己的数据 |
| 邮件和通知 | 通过 Resend 发送事务邮件,按收件人的语言本地化,并记录投递日志;应用内有通知铃铛 |
| 文件 | 上传到 R2,经 Worker 流式读写;用户头像 |
| 多语言 | 用 Paraglide 做英文和中文,英文不带前缀,中文走 /zh/... |
| 内容 | MDX 写的博客、文档和法律页面(服务条款、隐私政策、退款、DMCA),构建时编译 |
| 营销站 | 由配置数组拼出来的落地页、定价页,四套视觉主题,每套都有亮色和暗色 |
| 更多模块 | 审计日志、反馈、候补名单、推广返佣、PostHog 分析,以及公开演示模式 |
| 账号自助 | 个人资料、语言、导出 JSON 格式的个人数据,以及带保留期的账号注销 |
每一项在侧边栏的“功能”分组下都有单独的页面。
技术栈,以及为什么这样选
| 层 | 选择 | 原因 |
|---|---|---|
| 框架 | TanStack Start + TanStack Router 和 Query | 类型安全的文件路由、server function 和 loader 都在一个基于 Vite 的 React 应用里 |
| 运行时 | Cloudflare Workers | 一个部署目标,跑在边缘;dev server 把代码跑在 workerd 里,和线上是同一个运行时 |
| 数据库 | Cloudflare D1(SQLite)+ Drizzle | 是 binding 而不是连接串:没有连接池、没有凭据,本地和线上行为一致 |
| 存储 | Cloudflare R2 | 同样是 binding,上传不需要 S3 密钥,也不需要预签名 URL |
| 认证 | better-auth | 会话和 OAuth 都存在你自己的数据库里,One Tap、邮箱验证码、管理员和 API key 都有现成插件 |
| 支付 | Stripe 或 Creem | 两个都接好了,PAYMENT_PROVIDER 决定新订单走哪一家。见支付 |
| 多语言 | Paraglide | 文案编译成可以 tree-shake 的函数,没有运行时词典 |
| 内容 | 通过 @mdx-js/rollup 处理 MDX | 构建时编译成 ES 模块,因为 workerd 禁止大多数 MDX 方案依赖的运行时求值 |
| UI | shadcn/ui + Tailwind CSS v4 | 组件代码归你所有、随便改,样式走 token |
| 工具链 | bun、Biome、Vitest、Playwright | 一个包管理器,一个 lint 加格式化工具,单元测试和端到端测试 |
这些选择也带来几条限制,最好提前知道:
- TanStack Start 还是 v1 的 release candidate。版本都锁死了,升级前先读 changelog。
- D1 没有交互式事务。多步写入要写成一条带条件的语句,或者用
db.batch(),见数据库。 - 模板只面向 Cloudflare。换到别的平台意味着替换 binding 这一层,不是改个配置就行。
- 应用内切换套餐、续费失败处理,以及在支付服务商那边抹除客户信息,都只支持 Stripe。
仓库结构
src/
├── routes/ # 很薄的路由文件:守卫和组合
│ ├── _marketing/ # 公开页面,构建时预渲染
│ ├── _auth/ # 登录
│ ├── _app/ # 登录后的外壳:dashboard、billing、credits、
│ │ # files、settings 以及 /admin 后台
│ └── api/ # 认证、webhook、文件流、/api/v1 API
├── features/ # 垂直切片:auth、billing、credits、files……
├── core/ # 共享内核:db、server、payment、email、authz……
├── config/ # app-config、套餐、落地页、主题
└── components/ # 共享 UI、侧边栏、营销区块
content/ # MDX 写的博客、文档和法律页面
messages/ # 界面文案,en.json 和 zh.json
drizzle/ # SQL 迁移
scripts/ # 种子数据、删除配方、sitemap、营销截图
像 src/features/credits/ 这样的功能目录,自己持有 schema、server function、组件和query。它通过 src/core/ 里的几个小注册表接入应用的其余部分,每个功能一行 import,所以删除一个功能就是删掉它的目录和几行做了标记的代码。各部分怎么连起来,见架构。
界面上的每一次读写都走同一条路径:路由 loader,然后是 TanStack Query,然后是server function,最后是 Drizzle。API 路由只留给外部调用方:认证、webhook、文件流和/api/v1 这组 API。细节见 服务端函数。
接下来看什么
- 快速开始:在本地把应用跑起来,并把自己设为管理员。
- 配合 AI agent 开发:如果你用 Claude Code、Codex 或 Cursor,这里讲 skill 和兜底测试。
- 配置:配置文件、环境变量和后台可改的设置。
- 部署:部署到 Cloudflare。
- 删除功能:开始开发之前,先删掉用不到的模块。