A batteries-included TanStack Start admin panel starter kit, deployed to Cloudflare Workers. Authentication, role-based permissions, offline-first data, and a Cloudflare-native REST API come wired up — adapt it for any domain (ecommerce, CRM, property, health, etc.) rather than building the plumbing from scratch.
For the full architecture writeup — server layering, the frontend data-access decision matrix, naming conventions, and the PWA setup — see docs/ARCHITECTURE.md. This README is the quick-start summary.
| Layer | Tools |
|---|---|
| Frontend | React, TanStack Router (file-based), TanStack Query, TanStack DB, TanStack Form, Mantine, Tailwind CSS |
| Server | TanStack Start (SSR), Hono + @hono/zod-openapi for the REST API |
| Data | Drizzle ORM + Cloudflare D1, @tanstack/browser-db-sqlite-persistence (offline SQLite) + @tanstack/offline-transactions |
| Auth | better-auth — email/password, passkeys, API keys, role-based permissions, i18n |
| Infra | Cloudflare Workers, D1, Vite, Wrangler |
| i18n | i18next (English + Urdu, RTL-aware) |
| PWA | Installable manifest + service worker (workbox-build), offline app shell |
Requires Node.js and pnpm.
pnpm installCreate .dev.vars at the project root with the secrets better-auth needs locally:
BETTER_AUTH_SECRET=<any random string>
BETTER_AUTH_URL=http://localhost:3000
Apply the database migrations to your local D1 instance:
pnpm db:migrate:localStart the dev server:
pnpm devOpen http://localhost:3000 and go through /auth/setup to create the first account — it's automatically granted admin (sign-up is disabled for everyone after that). Alternatively, seed a batch of demo users with pnpm db:seed (creates 20 users, 4 of them admins, all with the password Password123! — local/demo use only).
src/
├── routes/ File-based TanStack Router routes — presentation + wiring only
├── components/ UI + interaction/mutation logic, grouped by area (admin/home/site/...)
├── hooks/ Small, generic, cross-cutting React hooks
├── lib/
│ ├── collections/ TanStack DB collection definitions (one file per domain)
│ ├── queries/ Plain TanStack Query `queryOptions` factories
│ ├── mutations/ Offline-transactions mutation functions
│ ├── db/ Browser persistence + the offline executor
│ └── i18n/ i18next config + en/ur locale resources
└── server/
├── services/ Pure Drizzle queries — no auth, no framework
├── actions/ TanStack Start server functions, consumed by the frontend
├── routes/
│ ├── platform/ Hono + OpenAPI — 1:1 mirror of actions, for automation/agent consumers
│ └── v1/ Hono + OpenAPI — public, versioned API for third parties
├── auth/ better-auth config, session/permission middleware
├── db/ Drizzle schema, migrations, D1 client
└── openapi/ Shared OpenAPI plumbing (docs UI, request/response models)
Every one-file-per-domain layer above is named <domain>.<category>.ts (e.g. notes.service.ts, notes.action.ts, notes.collection.ts) — see docs/ARCHITECTURE.md for why.
Once the dev server is running, interactive OpenAPI docs are served at:
/api/reference— combined reference (Auth,v1, andplatform)/api/v1/doc,/api/platform/doc— raw OpenAPI JSON per surface
| Script | What it does |
|---|---|
pnpm dev |
Start the Vite dev server (port 3000) |
pnpm build |
Production build (also generates the PWA service worker) |
pnpm preview |
Preview the production build locally |
pnpm deploy |
Build and deploy to Cloudflare Workers |
pnpm lint / pnpm format / pnpm check |
Lint, auto-fix + format, or check formatting |
pnpm db:generate |
Generate a new Drizzle migration from schema changes |
pnpm db:migrate:local / pnpm db:migrate:remote |
Apply migrations to the local or remote D1 database |
pnpm db:seed |
Seed demo users (local dev only) |
pnpm generate-routes |
Regenerate src/routeTree.gen.ts |
This project deploys to Cloudflare Workers via the Cloudflare Vite plugin and wrangler.jsonc (D1 binding, compatibility flags). To deploy:
wrangler login
pnpm db:migrate:remote
pnpm deploySet the same secrets .dev.vars holds locally as Worker secrets first:
wrangler secret put BETTER_AUTH_SECRET
wrangler secret put BETTER_AUTH_URL