| name | tanstack-start |
| description | TanStack Start full-stack React framework. Use for: server functions with createServerFn, TanStack Router file-based routing, TanStack Query SSR integration, Cloudflare Workers deployment. |
TanStack Start
Full-stack React framework with SSR, server functions, and Vite bundling.
Project Setup
New project:
pnpm create cloudflare@latest my-app --framework=tanstack-start -y --no-deploy
Existing app: See references/migration.md for converting React/Vite apps.
Critical Configuration
vite.config.ts
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 for complete guide.
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:
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:
// 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 loadersserver.handlers- API routes for client event handlerscreateMiddleware({ type: 'function' })- Reusable middleware@tanstack/react-start/server:getRequestHeaders(),setResponseHeader(),getCookies()
Routing
See 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) |
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 for complete guide.
pnpm add -D @cloudflare/vite-plugin wrangler
vite.config.ts - add cloudflare plugin:
import { cloudflare } from '@cloudflare/vite-plugin'
// Add to plugins: cloudflare({ viteEnvironment: { name: 'ssr' } })
wrangler.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:
import { env } from 'cloudflare:workers'
const value = await env.MY_KV.get('key')
Static Prerendering (v1.138.0+):
tanstackStart({ prerender: { enabled: true } })
TanStack Query Integration
See references/query-integration.md for SSR setup.
pnpm add @tanstack/react-query @tanstack/react-router-ssr-query
// Preload in loaders, consume with useSuspenseQuery
loader: ({ context }) => context.queryClient.ensureQueryData(myQueryOptions)
GitHub Actions Auto-Deploy
See references/github-actions-deploy.md for CI/CD setup with preview deployments.