$npx -y skills add jezweb/claude-skills --skill tanstack-startBuild a full-stack TanStack Start app on Cloudflare Workers from scratch — SSR, file-based routing, server functions, D1+Drizzle, better-auth, Tailwind v4+shadcn/ui. Use whenever the user mentions TanStack Start, asks to scaffold a full-stack Cloudflare app with SSR, wants an SSR
| 1 | # TanStack Start on Cloudflare |
| 2 | |
| 3 | Build a complete full-stack app from nothing. Claude generates every file — no template clone, no scaffold command. |
| 4 | |
| 5 | Stack: TanStack Start v1 (SSR, file-based routing, server functions via Nitro) on Cloudflare Workers; React 19 + Tailwind v4 + shadcn/ui; D1 + Drizzle; better-auth (Google OAuth + email/password). |
| 6 | |
| 7 | ## Project File Tree |
| 8 | |
| 9 | ``` |
| 10 | PROJECT_NAME/ |
| 11 | ├── src/ |
| 12 | │ ├── routes/ |
| 13 | │ │ ├── __root.tsx # Root layout (HTML shell, theme, CSS import) |
| 14 | │ │ ├── index.tsx # Landing / auth redirect |
| 15 | │ │ ├── login.tsx # Login page |
| 16 | │ │ ├── register.tsx # Register page |
| 17 | │ │ ├── _authed.tsx # Auth guard layout route |
| 18 | │ │ ├── _authed/ |
| 19 | │ │ │ ├── dashboard.tsx # Dashboard with stat cards |
| 20 | │ │ │ ├── items.tsx # Items list table |
| 21 | │ │ │ ├── items.$id.tsx # Edit item |
| 22 | │ │ │ └── items.new.tsx # Create item |
| 23 | │ │ └── api/ |
| 24 | │ │ └── auth/ |
| 25 | │ │ └── $.ts # better-auth API catch-all |
| 26 | │ ├── components/ |
| 27 | │ │ ├── ui/ # shadcn/ui components (auto-installed) |
| 28 | │ │ ├── app-sidebar.tsx # Navigation sidebar |
| 29 | │ │ ├── theme-toggle.tsx # Light/dark/system toggle |
| 30 | │ │ ├── user-nav.tsx # User dropdown menu |
| 31 | │ │ └── stat-card.tsx # Dashboard stat card |
| 32 | │ ├── db/ |
| 33 | │ │ ├── schema.ts # Drizzle schema (all tables) |
| 34 | │ │ └── index.ts # Drizzle client factory |
| 35 | │ ├── lib/ |
| 36 | │ │ ├── auth.server.ts # better-auth server config |
| 37 | │ │ ├── auth.client.ts # better-auth React hooks |
| 38 | │ │ └── utils.ts # cn() helper for shadcn/ui |
| 39 | │ ├── server/ |
| 40 | │ │ └── functions.ts # Server functions (CRUD, auth checks) |
| 41 | │ ├── styles/ |
| 42 | │ │ └── app.css # Tailwind v4 + shadcn/ui CSS variables |
| 43 | │ ├── router.tsx # TanStack Router configuration |
| 44 | │ ├── client.tsx # Client entry (hydrateRoot) |
| 45 | │ ├── ssr.tsx # SSR entry |
| 46 | │ └── routeTree.gen.ts # Auto-generated route tree (do not edit) |
| 47 | ├── drizzle/ # Generated migrations |
| 48 | ├── public/ # Static assets (favicon, etc.) |
| 49 | ├── vite.config.ts |
| 50 | ├── wrangler.jsonc |
| 51 | ├── drizzle.config.ts |
| 52 | ├── tsconfig.json |
| 53 | ├── package.json |
| 54 | ├── .dev.vars # Local env vars (NOT committed) |
| 55 | └── .gitignore |
| 56 | ``` |
| 57 | |
| 58 | ## Dependencies |
| 59 | |
| 60 | **Runtime:** |
| 61 | ```json |
| 62 | { |
| 63 | "react": "^19.0.0", |
| 64 | "react-dom": "^19.0.0", |
| 65 | "@tanstack/react-router": "^1.120.0", |
| 66 | "@tanstack/react-start": "^1.120.0", |
| 67 | "drizzle-orm": "^0.38.0", |
| 68 | "better-auth": "^1.2.0", |
| 69 | "zod": "^3.24.0", |
| 70 | "class-variance-authority": "^0.7.0", |
| 71 | "clsx": "^2.1.0", |
| 72 | "tailwind-merge": "^3.0.0", |
| 73 | "lucide-react": "^0.480.0" |
| 74 | } |
| 75 | ``` |
| 76 | |
| 77 | **Dev:** |
| 78 | ```json |
| 79 | { |
| 80 | "@cloudflare/vite-plugin": "^1.0.0", |
| 81 | "@tailwindcss/vite": "^4.0.0", |
| 82 | "@vitejs/plugin-react": "^4.4.0", |
| 83 | "tailwindcss": "^4.0.0", |
| 84 | "typescript": "^5.7.0", |
| 85 | "drizzle-kit": "^0.30.0", |
| 86 | "wrangler": "^4.0.0", |
| 87 | "tw-animate-css": "^1.2.0" |
| 88 | } |
| 89 | ``` |
| 90 | |
| 91 | **Scripts:** |
| 92 | ```json |
| 93 | { |
| 94 | "dev": "vite", |
| 95 | "build": "vite build", |
| 96 | "preview": "vite preview", |
| 97 | "deploy": "wrangler deploy", |
| 98 | "db:generate": "drizzle-kit generate", |
| 99 | "db:migrate:local": "wrangler d1 migrations apply PROJECT_NAME-db --local", |
| 100 | "db:migrate:remote": "wrangler d1 migrations apply PROJECT_NAME-db --remote" |
| 101 | } |
| 102 | ``` |
| 103 | |
| 104 | ## Workflow |
| 105 | |
| 106 | ### Step 1: Gather Project Info |
| 107 | |
| 108 | | Required | Optional | |
| 109 | |----------|----------| |
| 110 | | Project name (kebab-case) | Google OAuth credentials | |
| 111 | | One-line description | Custom domain | |
| 112 | | Cloudflare account | R2 storage needed? | |
| 113 | | Auth method: Google OAuth, email/password, or both | Admin email | |
| 114 | |
| 115 | ### Step 2: Initialise Project |
| 116 | |
| 117 | Create the project directory and all config files from scratch. |
| 118 | |
| 119 | **`vite.config.ts`** — Plugin order matters. Cloudflare MUST be first: |
| 120 | |
| 121 | ```typescript |
| 122 | import { defineConfig } from "vite"; |
| 123 | import { cloudflare } from "@cloudflare/vite-plugin"; |
| 124 | import { tanstackStart } from "@tanstack/react-start/plugin/vite"; |
| 125 | import tailwindcss from "@tailwindcss/vite"; |
| 126 | import viteReact from "@vitejs/plugin-react"; |
| 127 | |
| 128 | export default defineConfig({ |
| 129 | plugins: [ |
| 130 | cloudflare({ viteEnvironment: { name: "ssr" } }), |
| 131 | tailwindcss(), |
| 132 | tanstackStart(), |
| 133 | viteReact(), |
| 134 | ], |
| 135 | }); |
| 136 | ``` |
| 137 | |
| 138 | **`wrangler.jsonc`**: |
| 139 | |
| 140 | ```jsonc |
| 141 | { |
| 142 | "$schema": "node_modul |