A Bun + Turborepo monorepo template for shipping production apps on TanStack Start + Convex (via kitcn) with a custom session-token user system, a separate front-of-house app and admin dashboard sharing the same backend and design system.
| Layer | What it uses |
|---|---|
| Runtime | Bun 1.3.11, Turborepo 2.9 |
| Frontend | TanStack Start (Vite, SPA mode on Cloudflare Workers), TanStack Router, TanStack Query |
| Backend | Convex with kitcn procedure builders + ORM |
| Auth | Custom session-token user system (scrypt credentials, localStorage token, role-gated apps) |
| UI | Ant Design v6 (antd + @ant-design/icons), no CSS framework |
| Tooling | oxc (oxfmt + oxlint --type-aware), portless named-localhost dev URLs |
| Deploy | Cloudflare Workers via @cloudflare/vite-plugin (per-app wrangler.jsonc) |
Two apps share the backend:
apps/web— front-of-house, gatesrole === "user"apps/dashboard— admin, gatesrole === "admin"
- Bun ≥ 1.3.11 (required —
packageManageris pinned) - portless —
npm i -g portless - A Convex account
- (production) A Cloudflare Workers account (
wrangleris bundled as a dependency)
git clone git@github.com:MikeyZhang75/kitcn-tanstack-bootstrap.git my-app
cd my-app
bun installcd packages/backend
bunx convex dev --onceThis walks you through Convex login + project creation and writes CONVEX_DEPLOYMENT + CONVEX_URL to packages/backend/.env.local. Note the deployment URL; you'll wire it into the frontend next.
# from packages/backend/
bunx kitcn dev --oncebunx kitcn dev --once pushes the schema + functions to your new deployment. There are no runtime env vars or auth secrets to provision — the app uses its own session-token system (scrypt password hashing in Convex), not Better Auth or JWTs.
Create apps/web/.env.local:
VITE_CONVEX_URL=https://your-deployment.convex.cloud
VITE_CONVEX_SITE_URL=https://your-deployment.convex.site
VITE_SITE_URL=https://web.localhostCreate apps/dashboard/.env.local (same VITE_CONVEX_* values, different VITE_SITE_URL):
VITE_CONVEX_URL=https://your-deployment.convex.cloud
VITE_CONVEX_SITE_URL=https://your-deployment.convex.site
VITE_SITE_URL=https://dashboard.localhostportless routes *.localhost HTTPS through a daemon on port 443. It needs sudo once per machine boot — bun dev will register routes automatically but the proxy itself doesn't auto-start without elevation:
sudo portless proxy startThe proxy generates a local CA on first run and adds it to your system trust store, so no browser warnings. See docs/dev-environment.md for details.
From the repo root:
bun run devThis launches three Turbo tasks in parallel:
apps/web→ https://web.localhostapps/dashboard→ https://dashboard.localhostpackages/backend→kitcn dev(Convex dev + codegen watcher)
Signups always land as role: "user", so the dashboard (role === "admin") has no client signup path on a fresh deployment. Use the cold-start mutation once:
cd packages/backend
bunx convex run users:bootstrapAdmin '{"username":"admin","password":"<your-password>"}'Then sign in at https://dashboard.localhost with that username and password. See docs/auth.md for prod variants and the safety net (refuses to run if any admin already exists).
| Command | What it does |
|---|---|
bun run dev |
Run all dev servers (web + dashboard + backend codegen watcher) |
bun run build |
Production build of both apps |
bun run typecheck |
Type-check across all workspaces — the canonical correctness gate |
bun run check:fix |
Format with oxfmt + lint with oxlint --type-aware --fix |
bun run codegen |
Regenerate kitcn / Convex bindings (run after editing convex/functions/* or schema.ts) |
Backend-only:
cd packages/backend
bun run dev # kitcn dev (Convex + codegen watcher)
bun run deploy # kitcn deploy --yes (prod)Components come from Ant Design v6, imported directly in each app. There is no shared UI workspace and no CSS framework — no Tailwind, no className styling:
import { DashboardOutlined } from "@ant-design/icons";
import { Button, Flex, theme, Typography } from "antd";
const { token } = theme.useToken();Layout goes through antd's own primitives (Flex, Layout, Typography); everything else is an inline style fed by theme.useToken(). Locale (zh_CN) and theme are configured once in each app's src/components/providers.tsx, and toasts come from App.useApp() rather than the static message export.
See docs/ui-components.md for the shell, table, and form patterns plus the antd v6 API notes.
CI deploys on push to main (when the workflow is wired up):
- Convex via
kitcn deploy. - Both apps to Cloudflare Workers — each app's
deployscript runsvite build && wrangler deploy, readingapps/web/wrangler.jsonc/apps/dashboard/wrangler.jsonc.
Each app is built with its own VITE_SITE_URL (its public origin, validated in src/env.ts):
bun run build --filter=@repo/web # with VITE_SITE_URL=https://app.example.com
bun run build --filter=@repo/dashboard # with VITE_SITE_URL=https://dash.example.comFor schema changes that need a backfill (e.g. adding a .notNull() column), follow docs/MIGRATION.md — never deploy a .notNull() column before existing rows have the value set.
- Monorepo layout — workspaces, root scripts, deploy pipeline
- Backend architecture — kitcn procedure builders,
{ code, message, data? }cRPC envelope - Auth flow — custom session-token auth (no Better Auth/JWT), client-side role gate, cold-start admin
- Frontend architecture — TanStack Router layouts, FSD-style slices
- UI components — Ant Design v6, theme tokens, shell / table / form patterns
- Invitations feature — admin-minted signup codes, state machine
- Conventions —
useReducerrule, shared-schema-per-procedure, kitcn target asymmetry - Dev environment — portless setup, Conductor symlinks
- Migration guide — Convex schema changes with backfill workflow
- kitcn CLI guide —
deploy/migrate/aggregatetarget asymmetry - Version bumps — append-only log + bump procedure with subagent fan-out
MIT.