tanstack-start
TanStack Start full-stack React framework. Use for: server functions with createServerFn, TanStack Router file-based routing, TanStack Query SSR integration, Cloudflare Workers deployment.
What this skill does
# TanStack Start
Full-stack React framework with SSR, server functions, and Vite bundling.
## Project Setup
**New project:**
```bash
pnpm create cloudflare@latest my-app --framework=tanstack-start -y --no-deploy
```
**Existing app:** See [references/migration.md](references/migration.md) for converting React/Vite apps.
## Critical Configuration
### vite.config.ts
```typescript
import { defineConfig } from 'vite'
import tsConfigPaths from 'vite-tsconfig-paths'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import viteReact from '@vitejs/plugin-react'
export default defineConfig({
plugins: [
tsConfigPaths(),
tanstackStart(),
viteReact(), // MUST come AFTER tanstackStart
],
})
```
### tsconfig.json gotcha
Do NOT enable `verbatimModuleSyntax` - it leaks server bundles into client bundles.
## Server Functions
See [references/server-functions.md](references/server-functions.md) for complete guide.
> ⚠️ **Critical**: Route loaders run on server for initial SSR, but run on **CLIENT** during navigation. Always wrap server code in `createServerFn()` to ensure it runs server-side.
**When to use what (Cloudflare Workers):**
| Use Case | Solution |
|----------|----------|
| Server code in route loaders | `createServerFn()` |
| Server code from client event handlers | API routes (`server.handlers`) work best |
| Access Cloudflare bindings | `import { env } from 'cloudflare:workers'` |
**createServerFn** - for loaders:
```typescript
import { createServerFn } from '@tanstack/react-start'
export const getData = createServerFn().handler(async () => {
return { data: process.env.SECRET } // Server-only
})
```
**API routes** - for client event handlers:
```tsx
// routes/api/users.ts
export const Route = createFileRoute('/api/users')({
server: {
handlers: {
POST: async ({ request }) => {
const body = await request.json()
return Response.json(await db.users.create(body))
},
},
},
})
// In component: fetch('/api/users', { method: 'POST', body: JSON.stringify(data) })
```
**Key APIs:**
- `createServerFn()` - Server-only functions for loaders
- `server.handlers` - API routes for client event handlers
- `createMiddleware({ type: 'function' })` - Reusable middleware
- `@tanstack/react-start/server`: `getRequestHeaders()`, `setResponseHeader()`, `getCookies()`
## Routing
See [references/routing.md](references/routing.md) for complete patterns.
**File conventions** in `src/routes/`:
| Pattern | Route |
|---------|-------|
| `index.tsx` | `/` |
| `posts.$postId.tsx` | `/posts/:postId` |
| `_layout.tsx` | Layout (no URL) |
| `__root.tsx` | Root layout (required) |
```tsx
import { createFileRoute } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
const getPost = createServerFn()
.handler(async () => await db.post.findFirst())
export const Route = createFileRoute('/posts/$postId')({
loader: ({ params }) => getPost({ data: params.postId }),
component: () => {
const post = Route.useLoaderData()
return <h1>{post.title}</h1>
},
})
```
## Cloudflare Deployment
See [references/cloudflare-deployment.md](references/cloudflare-deployment.md) for complete guide.
```bash
pnpm add -D @cloudflare/vite-plugin wrangler
```
**vite.config.ts** - add cloudflare plugin:
```typescript
import { cloudflare } from '@cloudflare/vite-plugin'
// Add to plugins: cloudflare({ viteEnvironment: { name: 'ssr' } })
```
**wrangler.jsonc**:
```jsonc
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "my-app",
"compatibility_date": "<CURRENT_DATE>", // Use today's YYYY-MM-DD
"compatibility_flags": ["nodejs_compat"],
"main": "@tanstack/react-start/server-entry",
"observability": { "enabled": true }
}
```
**Access Cloudflare bindings** in server functions:
```typescript
import { env } from 'cloudflare:workers'
const value = await env.MY_KV.get('key')
```
**Static Prerendering** (v1.138.0+):
```typescript
tanstackStart({ prerender: { enabled: true } })
```
## TanStack Query Integration
See [references/query-integration.md](references/query-integration.md) for SSR setup.
```bash
pnpm add @tanstack/react-query @tanstack/react-router-ssr-query
```
```tsx
// Preload in loaders, consume with useSuspenseQuery
loader: ({ context }) => context.queryClient.ensureQueryData(myQueryOptions)
```
## Better-Auth Integration
See [references/better-auth.md](references/better-auth.md) for complete guide.
> ⚠️ **Critical**: Use `createFileRoute` with `server.handlers`, NOT the legacy `createAPIFileRoute`.
**Mount the auth handler** at `/src/routes/api/auth/$.ts`:
```typescript
import { auth } from '@/lib/auth'
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/api/auth/$')({
server: {
handlers: {
GET: ({ request }) => auth.handler(request),
POST: ({ request }) => auth.handler(request),
},
},
})
```
**Auth config** with `tanstackStartCookies` plugin:
```typescript
import { betterAuth } from "better-auth"
import { tanstackStartCookies } from "better-auth/tanstack-start"
export const auth = betterAuth({
// ...your config
plugins: [tanstackStartCookies()] // MUST be last plugin
})
```
**Protect routes** with middleware:
```typescript
import { createMiddleware } from "@tanstack/react-start"
import { getRequestHeaders } from "@tanstack/react-start/server"
import { auth } from "./auth"
export const authMiddleware = createMiddleware().server(
async ({ next }) => {
const session = await auth.api.getSession({ headers: getRequestHeaders() })
if (!session) throw redirect({ to: "/login" })
return next()
}
)
// In route:
export const Route = createFileRoute('/dashboard')({
server: { middleware: [authMiddleware] },
component: Dashboard,
})
```
## GitHub Actions Auto-Deploy
See [references/github-actions-deploy.md](references/github-actions-deploy.md) for CI/CD setup with preview deployments.
Related in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.