Introduction

What ShipKit is, what the template ships with, the stack it is built on and why, and how the repository is laid out.

ShipKit is a SaaS starter: a working application with sign-in, payments, credits, an admin console, email, i18n and a marketing site, running on TanStack Start and Cloudflare Workers. You clone it, rename it, delete what you do not need and build your product in the space that is left.

It is written around one rule: every optional feature must be deletable in minutes. Each module lives in its own folder, touches shared files only in marked places, and ships with a deletion recipe that CI runs on every pull request to prove the app still builds without it. The second design goal is that a coding agent can work in the repo without breaking it: the conventions are written down in CLAUDE.md and AGENTS.md, and the important ones are enforced by tests rather than trusted.

What you get

AreaWhat ships
Sign-inPasswordless, through better-auth: Google (with One Tap), GitHub when configured, and a six-digit code emailed to any address. No passwords, so no reset flow
PaymentsStripe and Creem behind one provider-neutral event layer: subscriptions, one-time credit packs, checkout, customer portal, refunds. Stripe is the default
CreditsA ledger with monthly grants per plan, purchasable packs, overdraft-proof spending and admin adjustments
Admin console/admin, in five sections: overview (KPIs and charts), people (users, waitlist, feedback), revenue (subscriptions, orders, credits, affiliates), developers (API keys, audit log, email log) and settings (operator settings, roles, email previews)
PermissionsRole-based access with wildcard grants. Each feature declares its permissions; the same grant gates the page, the server function and the API
APIRead-only JSON APIs with two kinds of API key: /api/v1/admin/* for your own analysis, /api/v1/me/* for a customer's own data
Email and notificationsTransactional email through Resend, localized per recipient, with a delivery log; an in-app notification bell
FilesUploads to R2, streamed through the Worker; profile photos
i18nEnglish and Chinese through Paraglide, English unprefixed and /zh/... for Chinese
ContentMDX blog, docs and legal pages (terms, privacy, refunds, DMCA), compiled at build time
Marketing siteA landing page assembled from a config array of blocks, a pricing page, four visual themes with light and dark modes
More modulesAudit log, feedback, waitlist, affiliate program, PostHog analytics, and a public demo mode
Account self-serviceProfile, language, data export as JSON, and account deletion with a retention window

Each of these has its own page under Features in the sidebar.

The stack, and why

LayerChoiceWhy
FrameworkTanStack Start + TanStack Router and QueryType-safe file routes, server functions and loaders in one React app, built on Vite
RuntimeCloudflare WorkersOne deploy target at the edge; the dev server runs your code in workerd, the same runtime as production
DatabaseCloudflare D1 (SQLite) + DrizzleA binding, not a connection string: no pool, no credentials, local and remote behave the same
StorageCloudflare R2Also a binding, so uploads need no S3 keys or presigned URLs
Authbetter-authSessions and OAuth in your own database, with plugins for One Tap, emailed codes, admin and API keys
PaymentsStripe or CreemBoth wired; PAYMENT_PROVIDER picks which takes new checkouts. See Payments
i18nParaglideMessages compile to tree-shakable functions; no runtime catalog
ContentMDX via @mdx-js/rollupCompiled to ES modules at build time, because workerd forbids the runtime evaluation most MDX layers rely on
UIshadcn/ui + Tailwind CSS v4Components you own and edit, styled with tokens
Toolingbun, Biome, Vitest, PlaywrightOne package manager, one linter and formatter, unit and end-to-end tests

A few limitations come with these choices, and it is better to know them up front:

  • TanStack Start is still a v1 release candidate. Versions are pinned exactly; read the changelog before you bump them.
  • D1 has no interactive transactions. Multi-step writes use a single conditional statement or db.batch(), as described in Database.
  • The template targets Cloudflare only. Moving to another host means replacing the bindings layer, not changing a setting.
  • In-app plan changes, failed-renewal handling and provider-side customer redaction are Stripe only.

How the repository is laid out

src/
├── routes/      # thin route files: guards and composition
│   ├── _marketing/   # public pages, prerendered at build
│   ├── _auth/        # login
│   ├── _app/         # signed-in shell: dashboard, billing, credits,
│   │                 #   files, settings and the /admin console
│   └── api/          # auth, webhooks, file streams, the /api/v1 APIs
├── features/    # vertical slices: auth, billing, credits, files, ...
├── core/        # shared kernel: db, server, payment, email, authz, ...
├── config/      # app-config, plans, landing page, themes
└── components/  # shared UI, the sidebar, marketing blocks
content/         # blog, docs and legal pages as MDX
messages/        # UI copy, en.json and zh.json
drizzle/         # SQL migrations
scripts/         # seed, deletion recipes, sitemap, marketing captures

A feature folder such as src/features/credits/ holds its own schema, server functions, components and queries. It reaches the rest of the app through small registries in src/core/, one import line per feature, so removing a feature means deleting its folder and a handful of marked lines. Architecture explains how the pieces connect.

Every read and write from the UI follows the same path: route loader, then TanStack Query, then a server function, then Drizzle. API routes are kept for external callers: auth, webhooks, file streams and the /api/v1 APIs. Server functions covers the details.

Where to go next

  • Getting started: run the app locally and make yourself an admin.
  • Working with AI agents: the skills and guard-rail tests, if you build with Claude Code, Codex or Cursor.
  • Configuration: the config files, environment variables and admin-editable settings.
  • Deploy: put it on Cloudflare.
  • Deleting features: strip the modules you do not need before you start building.