Skip to main content

tanstack-start

Build 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 dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per project.

Informations de source

Dépôt
jezweb/claude-skills
Dernière activité de la source
29 septembre 2026 à 04:23
Langue détectée de SKILL.md
anglais
Étoiles
1 034
Forks
106

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
tanstack-start
description
Build 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 dashboard, or asks for a React 19 + Cloudflare Workers app with file-based routing and server functions — even if they don't name TanStack Start specifically. No template repo — Claude generates every file fresh per project.
compatibility
claude-code-only
# TanStack Start on Cloudflare Build a complete full-stack app from nothing. Claude generates every file — no template clone, no scaffold command. 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). ## Project File Tree ``` PROJECT_NAME/ ├── src/ │ ├── routes/ │ │ ├── __root.tsx # Root layout (HTML shell, theme, CSS import) │ │ ├── index.tsx # Landing / auth redirect │ │ ├── login.tsx # Login page │ │ ├── register.tsx # Register page │ │ ├── _authed.tsx # Auth guard layout route │ │ ├── _authed/ │ │ │ ├── dashboard.tsx # Dashboard with stat cards │ │ │ ├── items.tsx # Items list table │ │ │ ├── items.$id.tsx # Edit item │ │ │ └── items.new.tsx # Create item │ │ └── api/ │ │ └── auth/ │ │ └── $.ts # better-auth API catch-all │ ├── components/ │ │ ├── ui/ # shadcn/ui components (auto-installed) │ │ ├── app-sidebar.tsx # Navigation sidebar │ │ ├── theme-toggle.tsx # Light/dark/system toggle │ │ ├── user-nav.tsx # User dropdown menu │ │ └── stat-card.tsx # Dashboard stat card │ ├── db/ │ │ ├── schema.ts # Drizzle schema (all tables) │ │ └── index.ts # Drizzle client factory │ ├── lib/ │ │ ├── auth.server.ts # better-auth server config │ │ ├── auth.client.ts # better-auth React hooks │ │ └── utils.ts # cn() helper for shadcn/ui │ ├── server/ │ │ └── functions.ts # Server functions (CRUD, auth checks) │ ├── styles/ │ │ └── app.css # Tailwind v4 + shadcn/ui CSS variables │ ├── router.tsx # TanStack Router configuration │ ├── client.tsx # Client entry (hydrateRoot) │ ├── ssr.tsx # SSR entry │ └── routeTree.gen.ts # Auto-generated route tree (do not edit) ├── drizzle/ # Generated migrations ├── public/ # Static assets (favicon, etc.) ├── vite.config.ts ├── wrangler.jsonc ├── drizzle.config.ts ├── tsconfig.json ├── package.json ├── .dev.vars # Local env vars (NOT committed) └── .gitignore ``` ## Dependencies **Runtime:** ```json { "react": "^19.0.0", "react-dom": "^19.0.0", "@tanstack/react-router": "^1.120.0", "@tanstack/react-start": "^1.120.0", "drizzle-orm": "^0.38.0", "better-auth": "^1.2.0", "zod": "^3.24.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "tailwind-merge": "^3.0.0", "lucide-react": "^0.480.0" } ``` **Dev:** ```json { "@cloudflare/vite-plugin": "^1.0.0", "@tailwindcss/vite": "^4.0.0", "@vitejs/plugin-react": "^4.4.0", "tailwindcss": "^4.0.0", "typescript": "^5.7.0", "drizzle-kit": "^0.30.0", "wrangler": "^4.0.0", "tw-animate-css": "^1.2.0" } ``` **Scripts:** ```json { "dev": "vite", "build": "vite build", "preview": "vite preview", "deploy": "wrangler deploy", "db:generate": "drizzle-kit generate", "db:migrate:local": "wrangler d1 migrations apply PROJECT_NAME-db --local", "db:migrate:remote": "wrangler d1 migrations apply PROJECT_NAME-db --remote" } ``` ## Workflow ### Step 1: Gather Project Info | Required | Optional | |----------|----------| | Project name (kebab-case) | Google OAuth credentials | | One-line description | Custom domain | | Cloudflare account | R2 storage needed? | | Auth method: Google OAuth, email/password, or both | Admin email | ### Step 2: Initialise Project Create the project directory and all config files from scratch. **`vite.config.ts`** — Plugin order matters. Cloudflare MUST be first: ```typescript import { defineConfig } from "vite"; import { cloudflare } from "@cloudflare/vite-plugin"; import { tanstackStart } from "@tanstack/react-start/plugin/vite"; import tailwindcss from "@tailwindcss/vite"; import viteReact from "@vitejs/plugin-react"; export default defineConfig({ plugins: [ cloudflare({ viteEnvironment: { name: "ssr" } }), tailwindcss(), tanstackStart(), viteReact(), ], }); ``` **`wrangler.jsonc`**: ```jsonc { "$schema": "node_modules/wrangler/config-schema.json", "name": "PROJECT_NAME", "compatibility_date": "2025-04-01", "compatibility_flags": ["nodejs_compat"], "main": "@tanstack/react-start/server-entry", "account_id": "ACCOUNT_ID", "d1_databases": [ { "binding": "DB", "database_name": "PROJECT_NAME-db", "database_id": "DATABASE_ID", "migrations_dir": "drizzle" } ] } ``` Key points: `main` MUST be `"@tanstack/react-start/server-entry"` (Nitro server entry). Use `nodejs_compat` (NOT `node_compat`). Add `account_id` to avoid interactive prompts. **`tsconfig.json`**: ```json { "compilerOptions": { "target": "ES2022", "module": "ESNext", "moduleResolution": "bundler", "jsx": "react-jsx", "strict": true, "skipLibCheck": true, "esModuleInterop": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "paths": { "@/*": ["./src/*"] }, "types": ["@cloudflare/workers-types/2023-07-01"] }, "include": ["src/**/*", "vite.config.ts"] } ``` **`.dev.vars`** — generate `BETTER_AUTH_SECRET` with `openssl rand -hex 32`: ``` BETTER_AUTH_SECRET=<generated-hex-32> BETTER_AUTH_URL=http://localhost:3000 TRUSTED_ORIGINS=http://localhost:3000 # GOOGLE_CLIENT_ID= # GOOGLE_CLIENT_SECRET= ``` **`.gitignore`** — node_modules, .wrangler, dist, .output, .dev.vars, .vinxi, .DS_Store Then install and create the D1 database: ```bash cd PROJECT_NAME && pnpm install npx wrangler d1 create PROJECT_NAME-db # Copy the database_id into wrangler.jsonc d1_databases binding ``` ### Step 3: Database Schema **`src/db/schema.ts`** — All tables. better-auth requires: `users`, `sessions`, `accounts`, `verifications`. Add application tables (e.g. `items`) for CRUD demo. D1-specific rules: - Use `integer` for timestamps (Unix epoch), NOT Date objects - Use `text` for primary keys (nanoid/cuid2), NOT autoincrement - Keep bound parameters under 100 per query (batch large inserts) - Foreign keys are always ON in D1 **`src/db/index.ts`** — Drizzle client factory: ```typescript import { drizzle } from "drizzle-orm/d1"; import { env } from "cloudflare:workers"; import * as schema from "./schema"; export function getDb() { return drizzle(env.DB, { schema }); } ``` **CRITICAL**: Use `import { env } from "cloudflare:workers"` — NOT `process.env`. Create the Drizzle client inside each server function (per-request), not at module level. **`drizzle.config.ts`**: ```typescript import { defineConfig } from "drizzle-kit"; export default defineConfig({ schema: "./src/db/schema.ts", out: "./drizzle", dialect: "sqlite", }); ``` Generate and apply the initial migration: ```bash pnpm db:generate pnpm db:migrate:local ``` ### Step 4: Configure Auth **`src/lib/auth.server.ts`** — Server-side better-auth: ```typescript import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { drizzle } from "drizzle-orm/d1"; import { env } from "cloudflare:workers"; import * as schema from "../db/schema"; export function getAuth() { const db = drizzle(env.DB, { schema }); return betterAuth({ database: drizzleAdapter(db, { provider: "sqlite" }), secret: env.BETTER_AUTH_SECRET, baseURL: env.BETTER_AUTH_URL, trustedOrigins: env.TRUSTED_ORIGINS?.split(",") ?? [], emailAndPassword: { enabled: true }, socialProviders: { // Add Google OAuth if credentials provided }, }); } ``` **CRITICAL**: `getAuth()` must be called per-request (inside handler/loader), NOT at module level. **`src/lib/auth.client.ts`** — Client-side auth hooks: ```typescript import { createAuthClient } from "better-auth/react"; export const { useSession, signIn, signOut, signUp } = createAuthClient(); ``` **`src/routes/api/auth/$.ts`** — API catch-all for better-auth: ```typescript import { createAPIFileRoute } from "@tanstack/react-start/api"; import { getAuth } from "../../../lib/auth.server"; export const APIRoute = createAPIFileRoute("/api/auth/$")({ GET: ({ request }) => getAuth().handler(request), POST: ({ request }) => getAuth().handler(request), }); ``` **CRITICAL**: Auth MUST use an API route (`createAPIFileRoute`), NOT a server function (`createServerFn`). better-auth needs direct request/response access. ### Step 5: Server Functions **Core pattern** — always create DB client inside the handler: ```typescript import { createServerFn } from "@tanstack/react-start"; import { getDb } from "../db"; export const getItems = createServerFn({ method: "GET" }).handler(async () => { const db = getDb(); return db.select().from(items).all(); }); ``` **Input validation** with Zod: ```typescript export const createItem = createServerFn({ method: "POST" }) .inputValidator( z.object({ name: z.string().min(1), description: z.string().optional(), }) ) .handler(async ({ data }) => { const db = getDb(); const id = crypto.randomUUID(); await db.insert(items).values({ id, ...data, createdAt: Date.now() }); return { id }; }); ``` **Protected server functions** — check auth, throw redirect if unauthenticated: ```typescript import { redirect } from "@tanstack/react-router"; import { getAuth } from "../lib/auth.server"; async function requireSession(request?: Request) { const auth = getAuth(); const session = await auth.api.getSession({ headers: request?.headers ?? new Headers(), }); if (!session) { throw redirect({ to: "/login" }); } return session; } export const getSessionFn = createServerFn({ method: "GET" }).handler( async ({ request }) => { const auth = getAuth(); return auth.api.getSession({ headers: request.headers }); } ); export const getItems = createServerFn({ method: "GET" }).handler(
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub