TanStack Start Guide
TanStack Start is a full-stack React framework powered by TanStack Router and Vite. It provides SSR, streaming, server functions, server routes, middleware, and universal deployment. If you only need client-side routing without SSR, streaming, server functions, or middleware, use TanStack Router directly instead of Start. Not for Next.js, Remix, or React Router.
Key differentiators: End-to-end type safety, composable middleware (client + server), selective SSR per route, deployment-agnostic (any Vite-compatible host), explicit over implicit patterns.
RSC support: Available via Composite Components — server-produced React components that the client fetches, caches, and streams. See references/migration.md for details.
Quick Start
pnpm create @tanstack/start@latest
# or
npm create @tanstack/start@latest
Or clone an official example:
npx gitpick TanStack/router/tree/main/examples/react/EXAMPLE_SLUG my-project
Official examples: start-basic, start-basic-auth, start-counter, start-basic-react-query, start-clerk-basic, start-convex-trellaux, start-supabase-basic, start-trellaux, start-workos, start-material-ui.
Manual Setup
Install dependencies:
npm i @tanstack/react-start @tanstack/react-router react react-dom
npm i -D vite @vitejs/plugin-react typescript vite-tsconfig-paths @types/node @types/react @types/react-dom
Required Files
vite.config.ts — Vite plugin configuration:
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()
],
})
Alternative React plugins: @vitejs/plugin-react-swc or @vitejs/plugin-react-oxc can replace @vitejs/plugin-react.
src/router.tsx — Router configuration:
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
export function getRouter() {
return createRouter({ routeTree, scrollRestoration: true })
}
src/routes/__root.tsx — Root route (HTML shell):
/// <reference types="vite/client" />
import type { ReactNode } from 'react'
import { Outlet, createRootRoute, HeadContent, Scripts } from '@tanstack/react-router'
export const Route = createRootRoute({
head: () => ({
meta: [
{ charSet: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ title: 'My App' },
],
}),
component: () => (
<html><head><HeadContent /></head>
<body><Outlet /><Scripts /></body>
</html>
),
})
package.json scripts:
{ "type": "module", "scripts": { "dev": "vite dev", "build": "vite build" } }
Core Workflow
1. File-Based Routing
Routes live in src/routes/. The routeTree.gen.ts is auto-generated on dev/build.
| Path |
Filename |
Type |
/ |
index.tsx |
Index |
/about |
about.tsx |
Static |
/posts/:id |
posts/$postId.tsx |
Dynamic |
/rest/* |
rest/$.tsx |
Wildcard |
| Layout wrapper |
_layout.tsx |
Pathless layout |
| Grouped dir |
(group)/route.tsx |
Organization only |
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => fetchPost(params.postId),
component: PostComponent,
})
function PostComponent() {
const post = Route.useLoaderData()
return <h1>{post.title}</h1>
}
2. Server Functions
Server-only logic callable from anywhere. Created with createServerFn():
import { createServerFn } from '@tanstack/react-start'
export const getUser = createServerFn({ method: 'GET' })
.inputValidator((data: { id: string }) => data)
.handler(async ({ data }) => {
return await db.users.find(data.id) // Runs only on server
})
// Call from loader, component, or other server function
const user = await getUser({ data: { id: '123' } })
Validation with Zod:
import { z } from 'zod'
export const createPost = createServerFn({ method: 'POST' })
.inputValidator(z.object({ title: z.string().min(1), body: z.string() }))
.handler(async ({ data }) => db.posts.create(data))
Redirects & errors:
import { redirect, notFound } from '@tanstack/react-router'
export const requireAuth = createServerFn().handler(async () => {
const user = await getSession()
if (!user) throw redirect({ to: '/login' })
return user
})
Progressive enhancement (no JS):
// Server functions have a .url property for HTML forms
<form method="POST" action={createPost.url}>
<input name="title" />
<button type="submit">Create</button>
</form>
3. Middleware
Two types: request middleware (all requests) and server function middleware (server functions only).
import { createMiddleware } from '@tanstack/react-start'
// Request middleware (server only)
const logger = createMiddleware().server(async ({ next, request }) => {
console.log(request.url)
return next()
})
// Server function middleware (client + server)
const auth = createMiddleware({ type: 'function' })
.client(async ({ next }) => next({ headers: { Authorization: `Bearer ${getToken()}` } }))
.server(async ({ next }) => next({ context: { user: await getUser() } }))
Global middleware via src/start.ts:
import { createStart } from '@tanstack/react-start'
export const startInstance = createStart(() => ({
requestMiddleware: [loggerMiddleware],
functionMiddleware: [authMiddleware],
}))
4. Server Routes (API Endpoints)
export const Route = createFileRoute('/api/hello')({
server: {
handlers: {
GET: async ({ request }) => Response.json({ message: 'Hello!' }),
POST: async ({ request }) => {
const body = await request.json()
return Response.json({ received: body })
},
},
},
})
5. Sessions
import { useSession } from '@tanstack/react-start/server'
function useAppSession() {
return useSession<{ userId?: string }>({
password: process.env.SESSION_SECRET!,
cookie: { secure: process.env.NODE_ENV === 'production', httpOnly: true, sameSite: 'lax' },
})
}
Key Rules & Constraints
Loaders are ISOMORPHIC — they run on server during SSR AND on client during navigation. Never access process.env secrets directly in loaders. Use createServerFn() instead.
Environment variables — Server: process.env.ANY_VAR. Client: only import.meta.env.VITE_* prefixed. Never prefix secrets with VITE_.
Plugin order — tanstackStart() MUST come before viteReact() in vite.config.ts.
TypeScript — Do NOT enable verbatimModuleSyntax (causes server bundle leaking into client). Required settings: jsx: "react-jsx", moduleResolution: "Bundler", module: "ESNext".
Server function imports — Safe to statically import anywhere. Avoid dynamic imports for server functions.
Execution boundaries — Use createServerOnlyFn() for server-only utilities that throw on client. Use createClientOnlyFn() for browser-only utilities. Use createIsomorphicFn() for environment-specific implementations.
Head management — <HeadContent /> in <head>, <Scripts /> at end of <body>. Both required in root route.
Raw Response — Server functions can return Response objects directly for binary data or custom content types.
Error Boundaries
Route-level error boundaries with default + per-route override:
// src/router.tsx — default for all routes
export function getRouter() {
return createRouter({
routeTree,
defaultErrorComponent: ({ error, reset }) => <ErrorComponent error={error} />,
})
}
// Per-route override
export const Route = createFileRoute('/posts/$postId')({
errorComponent: ({ error, reset }: ErrorComponentProps) => (
<div><p>Error: {error.message}</p><button
),
})
Common Errors
- Hydration mismatch: Caused by
Date.now(), Math.random(), locale-dependent APIs in SSR. Fix: use <ClientOnly>, useHydrated(), or suppressHydrationWarning.
- Env var undefined on client: Missing
VITE_ prefix. Restart dev server after adding new vars.
- Secret exposed to client: Used
process.env in loader (isomorphic!). Move to createServerFn().
- Bundle includes server code: Check for accidental dynamic imports of server functions.
Reference Files
references/api-routing.md — Routing, createFileRoute, createRootRoute, route hooks, components, error boundaries, navigation
references/api-server-functions.md — createServerFn, validation, streaming, server context utilities, useSession, environment functions, useServerFn
references/api-middleware.md — createMiddleware, createStart, custom fetch, header merging, fetch override, createHandlers
references/server-entry.md — Custom server/client entry, request context, handler callbacks, Cloudflare Workers extensions
references/configuration.md — Vite config options, TypeScript, environment variables, path aliases, Tailwind CSS, server build config
references/auth-sessions.md — Authentication (DIY + partners), sessions, route protection, RBAC, OAuth, password reset, rate limiting
references/data-streaming.md — Data loading patterns, streaming (async generators, ReadableStream), cache control, TanStack Query integration
references/seo-llmo.md — SEO meta tags, JSON-LD structured data, LLMO/AIO optimization, sitemaps, robots.txt, llms.txt
references/server-routes.md — API endpoints, dynamic params, wildcard routes, request bodies, per-handler middleware, escaped file names
references/patterns.md — Markdown rendering, database integration (Neon/Convex/Prisma), file organization, progressive enhancement, execution model, tutorials
references/deployment.md — Cloudflare Workers, Netlify, Railway, Vercel, Node.js/Docker, Bun, Appwrite Sites, Nitro
references/prerendering-caching.md — Static prerendering (SSG), ISR, selective SSR, SPA mode, CDN asset URLs
references/observability.md — Sentry, New Relic, OpenTelemetry, health checks, metrics collection, logging
references/migration.md — Next.js migration guide, framework comparison (Start vs Next.js vs React Router)
references/troubleshooting.md — Hydration errors, env variable issues, loader mistakes, middleware problems, production checklist
1---2name: tanstack-start-guide3description: Guide for TanStack Start — a full-stack React framework powered by TanStack Router and Vite with SSR, streaming, server functions, server routes, and middleware. Use when user asks to "create a TanStack Start app", "set up server functions", "configure TanStack Start", "add SSR to React app", "create API routes with Start", "set up middleware in Start", "deploy TanStack Start", "use createServerFn", "add authentication in Start", "stream data from server functions", "prerender pages", "use SPA mode", "migrate from Next.js to TanStack Start", "set up Tailwind with Start", "configure environment variables", "use sessions in Start", "add SEO meta tags", "use createMiddleware", "configure createStart", "set up src/start.ts", or asks about @tanstack/react-start, createStart, server function validation, file-based routing, selective SSR, Start deployment, ISR, CDN assets, observability, or LLMO optimization. Do NOT use for TanStack Router-only questions (without SSR/Start context), Next.js-only questions, Remix que4---56# TanStack Start Guide78TanStack Start is a full-stack React framework powered by TanStack Router and Vite. It provides SSR, streaming, server functions, server routes, middleware, and universal deployment. If you only need client-side routing without SSR, streaming, server functions, or middleware, use TanStack Router directly instead of Start. Not for Next.js, Remix, or React Router.910**Key differentiators:** End-to-end type safety, composable middleware (client + server), selective SSR per route, deployment-agnostic (any Vite-compatible host), explicit over implicit patterns.1112**RSC support:** Available via Composite Components — server-produced React components that the client fetches, caches, and streams. See `references/migration.md` for details.1314## Quick Start1516```bash17pnpm create @tanstack/start@latest18# or19npm create @tanstack/start@latest20```2122Or clone an official example:2324```bash25npx gitpick TanStack/router/tree/main/examples/react/EXAMPLE_SLUG my-project26```2728Official examples: `start-basic`, `start-basic-auth`, `start-counter`, `start-basic-react-query`, `start-clerk-basic`, `start-convex-trellaux`, `start-supabase-basic`, `start-trellaux`, `start-workos`, `start-material-ui`.2930### Manual Setup3132Install dependencies:3334```bash35npm i @tanstack/react-start @tanstack/react-router react react-dom36npm i -D vite @vitejs/plugin-react typescript vite-tsconfig-paths @types/node @types/react @types/react-dom37```3839### Required Files4041**vite.config.ts** — Vite plugin configuration:4243```ts44import { defineConfig } from 'vite'45import tsConfigPaths from 'vite-tsconfig-paths'46import { tanstackStart } from '@tanstack/react-start/plugin/vite'47import viteReact from '@vitejs/plugin-react'4849export default defineConfig({50 plugins: [51 tsConfigPaths(),52 tanstackStart(),53 viteReact(), // MUST come after tanstackStart()54 ],55})56```5758> Alternative React plugins: `@vitejs/plugin-react-swc` or `@vitejs/plugin-react-oxc` can replace `@vitejs/plugin-react`.5960**src/router.tsx** — Router configuration:6162```tsx63import { createRouter } from '@tanstack/react-router'64import { routeTree } from './routeTree.gen'6566export function getRouter() {67 return createRouter({ routeTree, scrollRestoration: true })68}69```7071**src/routes/__root.tsx** — Root route (HTML shell):7273```tsx74/// <reference types="vite/client" />75import type { ReactNode } from 'react'76import { Outlet, createRootRoute, HeadContent, Scripts } from '@tanstack/react-router'7778export const Route = createRootRoute({79 head: () => ({80 meta: [81 { charSet: 'utf-8' },82 { name: 'viewport', content: 'width=device-width, initial-scale=1' },83 { title: 'My App' },84 ],85 }),86 component: () => (87 <html><head><HeadContent /></head>88 <body><Outlet /><Scripts /></body>89 </html>90 ),91})92```9394**package.json** scripts:9596```json97{ "type": "module", "scripts": { "dev": "vite dev", "build": "vite build" } }98```99100## Core Workflow101102### 1. File-Based Routing103104Routes live in `src/routes/`. The `routeTree.gen.ts` is auto-generated on `dev`/`build`.105106| Path | Filename | Type |107|------|----------|------|108| `/` | `index.tsx` | Index |109| `/about` | `about.tsx` | Static |110| `/posts/:id` | `posts/$postId.tsx` | Dynamic |111| `/rest/*` | `rest/$.tsx` | Wildcard |112| Layout wrapper | `_layout.tsx` | Pathless layout |113| Grouped dir | `(group)/route.tsx` | Organization only |114115```tsx116import { createFileRoute } from '@tanstack/react-router'117118export const Route = createFileRoute('/posts/$postId')({119 loader: async ({ params }) => fetchPost(params.postId),120 component: PostComponent,121})122123function PostComponent() {124 const post = Route.useLoaderData()125 return <h1>{post.title}</h1>126}127```128129### 2. Server Functions130131Server-only logic callable from anywhere. Created with `createServerFn()`:132133```tsx134import { createServerFn } from '@tanstack/react-start'135136export const getUser = createServerFn({ method: 'GET' })137 .inputValidator((data: { id: string }) => data)138 .handler(async ({ data }) => {139 return await db.users.find(data.id) // Runs only on server140 })141142// Call from loader, component, or other server function143const user = await getUser({ data: { id: '123' } })144```145146**Validation with Zod:**147148```tsx149import { z } from 'zod'150151export const createPost = createServerFn({ method: 'POST' })152 .inputValidator(z.object({ title: z.string().min(1), body: z.string() }))153 .handler(async ({ data }) => db.posts.create(data))154```155156**Redirects & errors:**157158```tsx159import { redirect, notFound } from '@tanstack/react-router'160161export const requireAuth = createServerFn().handler(async () => {162 const user = await getSession()163 if (!user) throw redirect({ to: '/login' })164 return user165})166```167168**Progressive enhancement (no JS):**169170```tsx171// Server functions have a .url property for HTML forms172<form method="POST" action={createPost.url}>173 <input name="title" />174 <button type="submit">Create</button>175</form>176```177178### 3. Middleware179180Two types: **request middleware** (all requests) and **server function middleware** (server functions only).181182```tsx183import { createMiddleware } from '@tanstack/react-start'184185// Request middleware (server only)186const logger = createMiddleware().server(async ({ next, request }) => {187 console.log(request.url)188 return next()189})190191// Server function middleware (client + server)192const auth = createMiddleware({ type: 'function' })193 .client(async ({ next }) => next({ headers: { Authorization: `Bearer ${getToken()}` } }))194 .server(async ({ next }) => next({ context: { user: await getUser() } }))195```196197**Global middleware** via `src/start.ts`:198199```tsx200import { createStart } from '@tanstack/react-start'201202export const startInstance = createStart(() => ({203 requestMiddleware: [loggerMiddleware],204 functionMiddleware: [authMiddleware],205}))206```207208### 4. Server Routes (API Endpoints)209210```tsx211export const Route = createFileRoute('/api/hello')({212 server: {213 handlers: {214 GET: async ({ request }) => Response.json({ message: 'Hello!' }),215 POST: async ({ request }) => {216 const body = await request.json()217 return Response.json({ received: body })218 },219 },220 },221})222```223224### 5. Sessions225226```tsx227import { useSession } from '@tanstack/react-start/server'228229function useAppSession() {230 return useSession<{ userId?: string }>({231 password: process.env.SESSION_SECRET!,232 cookie: { secure: process.env.NODE_ENV === 'production', httpOnly: true, sameSite: 'lax' },233 })234}235```236237## Key Rules & Constraints2382391. **Loaders are ISOMORPHIC** — they run on server during SSR AND on client during navigation. Never access `process.env` secrets directly in loaders. Use `createServerFn()` instead.2402412. **Environment variables** — Server: `process.env.ANY_VAR`. Client: only `import.meta.env.VITE_*` prefixed. Never prefix secrets with `VITE_`.2422433. **Plugin order** — `tanstackStart()` MUST come before `viteReact()` in vite.config.ts.2442454. **TypeScript** — Do NOT enable `verbatimModuleSyntax` (causes server bundle leaking into client). Required settings: `jsx: "react-jsx"`, `moduleResolution: "Bundler"`, `module: "ESNext"`.2462475. **Server function imports** — Safe to statically import anywhere. Avoid dynamic imports for server functions.2482496. **Execution boundaries** — Use `createServerOnlyFn()` for server-only utilities that throw on client. Use `createClientOnlyFn()` for browser-only utilities. Use `createIsomorphicFn()` for environment-specific implementations.2502517. **Head management** — `<HeadContent />` in `<head>`, `<Scripts />` at end of `<body>`. Both required in root route.2522538. **Raw Response** — Server functions can return `Response` objects directly for binary data or custom content types.254255## Error Boundaries256257Route-level error boundaries with default + per-route override:258259```tsx260// src/router.tsx — default for all routes261export function getRouter() {262 return createRouter({263 routeTree,264 defaultErrorComponent: ({ error, reset }) => <ErrorComponent error={error} />,265 })266}267268// Per-route override269export const Route = createFileRoute('/posts/$postId')({270 errorComponent: ({ error, reset }: ErrorComponentProps) => (271 <div><p>Error: {error.message}</p><button onClick={reset}>Retry</button></div>272 ),273})274```275276## Common Errors277278- **Hydration mismatch**: Caused by `Date.now()`, `Math.random()`, locale-dependent APIs in SSR. Fix: use `<ClientOnly>`, `useHydrated()`, or `suppressHydrationWarning`.279- **Env var undefined on client**: Missing `VITE_` prefix. Restart dev server after adding new vars.280- **Secret exposed to client**: Used `process.env` in loader (isomorphic!). Move to `createServerFn()`.281- **Bundle includes server code**: Check for accidental dynamic imports of server functions.282283## Reference Files284285- `references/api-routing.md` — Routing, createFileRoute, createRootRoute, route hooks, components, error boundaries, navigation286- `references/api-server-functions.md` — createServerFn, validation, streaming, server context utilities, useSession, environment functions, useServerFn287- `references/api-middleware.md` — createMiddleware, createStart, custom fetch, header merging, fetch override, createHandlers288- `references/server-entry.md` — Custom server/client entry, request context, handler callbacks, Cloudflare Workers extensions289- `references/configuration.md` — Vite config options, TypeScript, environment variables, path aliases, Tailwind CSS, server build config290- `references/auth-sessions.md` — Authentication (DIY + partners), sessions, route protection, RBAC, OAuth, password reset, rate limiting291- `references/data-streaming.md` — Data loading patterns, streaming (async generators, ReadableStream), cache control, TanStack Query integration292- `references/seo-llmo.md` — SEO meta tags, JSON-LD structured data, LLMO/AIO optimization, sitemaps, robots.txt, llms.txt293- `references/server-routes.md` — API endpoints, dynamic params, wildcard routes, request bodies, per-handler middleware, escaped file names294- `references/patterns.md` — Markdown rendering, database integration (Neon/Convex/Prisma), file organization, progressive enhancement, execution model, tutorials295- `references/deployment.md` — Cloudflare Workers, Netlify, Railway, Vercel, Node.js/Docker, Bun, Appwrite Sites, Nitro296- `references/prerendering-caching.md` — Static prerendering (SSG), ISR, selective SSR, SPA mode, CDN asset URLs297- `references/observability.md` — Sentry, New Relic, OpenTelemetry, health checks, metrics collection, logging298- `references/migration.md` — Next.js migration guide, framework comparison (Start vs Next.js vs React Router)299- `references/troubleshooting.md` — Hydration errors, env variable issues, loader mistakes, middleware problems, production checklist