简介

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 方案依赖的运行时求值
UIshadcn/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。
  • 删除功能:开始开发之前,先删掉用不到的模块。