tanstack-start

安装量: 866
排名: #1491

安装

npx skills add https://github.com/jezweb/claude-skills --skill tanstack-start

TanStack Start on Cloudflare Build a complete full-stack app from nothing. Claude generates every file — no template clone, no scaffold command. Each project gets exactly what it needs. What You Get Layer Technology Framework TanStack Start v1 (SSR, file-based routing, server functions) Frontend React 19, Tailwind v4, shadcn/ui Backend Server functions (via Nitro on Cloudflare Workers) Database D1 + Drizzle ORM Auth better-auth (Google OAuth + email/password) Deployment Cloudflare Workers Workflow Step 1: Gather Project Info Ask for: 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. See references/architecture.md for the complete file tree, all dependencies, and config templates. Create these files first: package.json — all runtime + dev dependencies with version ranges from architecture.md tsconfig.json — strict mode, @/ path alias mapped to src/ vite.config.ts — plugins in correct order: cloudflare() → tailwindcss() → tanstackStart() → viteReact() wrangler.jsonc — main: "@tanstack/react-start/server-entry" , nodejs_compat flag, D1 binding placeholder .dev.vars — generate BETTER_AUTH_SECRET with openssl rand -hex 32 , set BETTER_AUTH_URL=http://localhost:3000 , TRUSTED_ORIGINS=http://localhost:3000 .gitignore — node_modules, .wrangler, dist, .output, .dev.vars, .vinxi, .DS_Store Then: cd PROJECT_NAME pnpm install Create D1 database and update wrangler.jsonc: npx wrangler d1 create PROJECT_NAME-db

Copy the database_id into wrangler.jsonc d1_databases binding

Step 3: Database Schema
Create the Drizzle schema with D1-correct patterns.
src/db/schema.ts
— Define all tables:
better-auth tables
:
users
,
sessions
,
accounts
,
verifications
— these are required by better-auth
Application table
:
items
(or whatever the project needs) 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:
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
. This is a per-request binding, so create the Drizzle client inside each server function, not at module level.
drizzle.config.ts
:
import
{
defineConfig
}
from
"drizzle-kit"
;
export
default
defineConfig
(
{
schema
:
"./src/db/schema.ts"
,
out
:
"./drizzle"
,
dialect
:
"sqlite"
,
}
)
;
Add migration scripts to
package.json
:
{
"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"
}
Generate and apply the initial migration:
pnpm
db:generate
pnpm
db:migrate:local
Step 4: Configure Auth
src/lib/auth.server.ts
— Server-side better-auth configuration:
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. The
env
import from
cloudflare:workers
is only available during request handling.
src/lib/auth.client.ts
— Client-side auth hooks:
import
{
createAuthClient
}
from
"better-auth/react"
;
export
const
{
useSession
,
signIn
,
signOut
,
signUp
}
=
createAuthClient
(
)
;
src/routes/api/auth/$.ts
— API catch-all route for better-auth:
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: App Shell + Theme src/routes/__root.tsx — Root layout with HTML document: Render full HTML document with and from @tanstack/react-router Add suppressHydrationWarning on
for SSR + theme toggle compatibility Import the global CSS file Include theme initialisation script inline to prevent flash of wrong theme src/styles/app.css — Tailwind v4 + shadcn/ui: @import "tailwindcss" (v4 syntax) CSS variables for shadcn/ui tokens in :root and .dark Neutral/monochrome palette (stone, slate, zinc) Use semantic tokens only — never raw Tailwind colours src/router.tsx — Router configuration: import { createRouter as createTanStackRouter } from "@tanstack/react-router" ; import { routeTree } from "./routeTree.gen" ; export function createRouter ( ) { return createTanStackRouter ( { routeTree } ) ; } declare module "@tanstack/react-router" { interface Register { router : ReturnType < typeof createRouter > ; } } src/client.tsx and src/ssr.tsx — Entry points (standard TanStack Start boilerplate). Install shadcn/ui components needed for the dashboard: pnpm dlx shadcn@latest init --defaults pnpm dlx shadcn@latest add button card input label sidebar table dropdown-menu form separator sheet Note : Configure shadcn to use src/components as the components directory. Theme toggle: three-state (light → dark → system → light). Store preference in localStorage. Apply .dark class on . Use JS-only system preference detection — NO CSS @media (prefers-color-scheme) queries. Step 6: Routes + Dashboard Create the route files: src/routes/ ├── __root.tsx # Root layout (HTML shell, theme, CSS) ├── index.tsx # Landing → redirect to /dashboard if authenticated ├── login.tsx # Login form (email/password + Google OAuth button) ├── register.tsx # Registration form ├── _authed.tsx # Auth guard layout (beforeLoad checks session) └── _authed/ ├── dashboard.tsx # Stat cards overview ├── items.tsx # Items list (table with actions) ├── items.$id.tsx # Edit item form └── items.new.tsx # Create item form Auth guard pattern ( _authed.tsx ): import { createFileRoute , redirect } from "@tanstack/react-router" ; import { getSessionFn } from "../server/auth" ; export const Route = createFileRoute ( "/_authed" ) ( { beforeLoad : async ( ) => { const session = await getSessionFn ( ) ; if ( ! session ) { throw redirect ( { to : "/login" } ) ; } return { session } ; } , } ) ; Components (in src/components/ ): app-sidebar.tsx — shadcn Sidebar with navigation links (Dashboard, Items) theme-toggle.tsx — three-state theme toggle button user-nav.tsx — user dropdown menu with sign-out action stat-card.tsx — reusable stat card for the dashboard See references/server-functions.md for createServerFn patterns used in route loaders and mutations. Step 7: CRUD Server Functions Create server functions for the items resource: Function Method Purpose getItems GET List all items for current user getItem GET Get single item by ID createItem POST Create new item updateItem POST Update existing item deleteItem POST Delete item by ID Each server function: Gets auth session (redirect if not authenticated) Creates per-request Drizzle client via getDb() Performs the database operation Returns typed data Route loaders call GET server functions. Mutations call POST server functions then router.invalidate() to refetch. Step 8: Verify Locally pnpm dev Verification checklist: App loads at http://localhost:3000 Register a new account (email/password) Login and logout work Dashboard page loads with stat cards Create a new item via /items/new Items list shows the new item Edit item via /items/:id Delete item from the list Theme toggle cycles: light → dark → system Sidebar collapses on mobile viewports No console errors Step 9: Deploy to Production # Set production secrets openssl rand -hex 32 | npx wrangler secret put BETTER_AUTH_SECRET echo "https://PROJECT.SUBDOMAIN.workers.dev" | npx wrangler secret put BETTER_AUTH_URL echo "http://localhost:3000,https://PROJECT.SUBDOMAIN.workers.dev" | npx wrangler secret put TRUSTED_ORIGINS # If using Google OAuth echo "your-client-id" | npx wrangler secret put GOOGLE_CLIENT_ID echo "your-client-secret" | npx wrangler secret put GOOGLE_CLIENT_SECRET # Migrate remote database pnpm db:migrate:remote # Build and deploy pnpm build && npx wrangler deploy After first deploy : Update BETTER_AUTH_URL with your actual Worker URL. Add production URL to Google OAuth redirect URIs: https://your-app.workers.dev/api/auth/callback/google . See references/deployment.md for the full production checklist and common mistakes. Common Issues Symptom Fix env is undefined in server function Use import { env } from "cloudflare:workers" — must be inside request handler, not module scope D1 database not found Check wrangler.jsonc d1_databases binding name matches code Auth redirect loop BETTER_AUTH_URL must match actual URL exactly (protocol + domain, no trailing slash) Auth silently fails (redirects to home) Set TRUSTED_ORIGINS secret with all valid URLs (comma-separated) Styles not loading in dev Ensure @tailwindcss/vite plugin is in vite.config.ts SSR hydration mismatch Add suppressHydrationWarning to element Build fails on Cloudflare Check nodejs_compat in compatibility_flags, main field in wrangler.jsonc Secrets not taking effect wrangler secret put does NOT redeploy — run npx wrangler deploy after
返回排行榜