| name | tanstack-start |
| description | Build full-stack React applications with TanStack Start. Use when creating, configuring, or working with TanStack Start projects, routing, server functions, middleware, deployment, or when the user mentions tanstack/start. |
TanStack Start Documentation
Full-stack React framework powered by TanStack Router and Vite. Provides SSR, streaming, server functions, API routes, middleware, and type-safe routing.
Documentation: https://tanstack.com/start/latest/docs/
Getting Started
Core Concepts
Server Features
Data and Loading
Rendering and Deployment
Configuration and Tooling
Guides
Examples
Key Packages
@tanstack/react-start - Core framework package
@tanstack/react-router - Type-safe router (required)
@tanstack/zod-adapter - Zod validation adapter for middleware/server functions
Operational Patterns
Project Structure
myApp/
src/
routes/
__root.tsx # Root layout (required, wraps all routes)
index.tsx # / route
about.tsx # /about route
posts/
index.tsx # /posts route
$postId.tsx # /posts/:postId dynamic route
router.tsx # Router configuration
routeTree.gen.ts # Auto-generated (do not edit)
vite.config.ts
package.json
tsconfig.json
Router Configuration
import { createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
export function getRouter() {
const router = createRouter({
routeTree,
scrollRestoration: true,
})
return router
}
Root Route (Required)
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: RootComponent,
})
function RootComponent() {
return (
<RootDocument>
<Outlet />
</RootDocument>
)
}
function RootDocument({ children }: Readonly<{ children: ReactNode }>) {
return (
<html>
<head>
<HeadContent />
</head>
<body>
{children}
<Scripts />
</body>
</html>
)
}
Creating Routes
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/posts/$postId')({
component: PostComponent,
loader: ({ params }) => fetchPost(params.postId),
})
function PostComponent() {
const post = Route.useLoaderData()
return <div>{post.title}</div>
}
Route File Naming Conventions
| Path | Filename | Type |
|---|
/ | index.tsx | Index route |
/about | about.tsx | Static route |
/posts/ | posts/index.tsx | Nested index route |
/posts/:id | posts/$postId.tsx | Dynamic route |
/rest/* | rest/$.tsx | Splat/wildcard route |
Server Functions
import { createServerFn } from '@tanstack/react-start'
export const getServerTime = createServerFn().handler(async () => {
return new Date().toISOString()
})
export const getPost = createServerFn({ method: 'GET' })
.inputValidator((data: { id: string }) => data)
.handler(async ({ data }) => {
return db.query.posts.findFirst({ where: eq(posts.id, data.id) })
})
export const createPost = createServerFn({ method: 'POST' })
.inputValidator((data: { title: string; body: string }) => data)
.handler(async ({ data }) => {
return db.insert(posts).values(data)
})
const time = await getServerTime()
const post = await getPost({ data: { id: '123' } })
await createPost({ data: { title: 'New', body: '...' } })
Calling Server Functions
export const Route = createFileRoute('/posts')({
loader: () => getServerPosts(),
})
function Post() {
const router = useRouter()
return (
<button onClick={() => createPost({ data: { title: 'New', body: '...' } }).then(() => router.invalidate())}>
Create
</button>
)
}
import { useServerFn } from '@tanstack/react-start'
function PostList() {
const getPosts = useServerFn(getServerPosts)
const { data } = useQuery({
queryKey: ['posts'],
queryFn: () => getPosts(),
})
}
Server Function Organization
src/utils/
users.functions.ts # createServerFn wrappers (safe to import anywhere)
users.server.ts # Server-only helpers (DB queries, internal logic)
schemas.ts # Shared validation schemas (client-safe)
import { db } from '~/db'
export async function findUserById(id: string) {
return db.query.users.findFirst({ where: eq(users.id, id) })
}
import { createServerFn } from '@tanstack/react-start'
import { findUserById } from './users.server'
export const getUser = createServerFn({ method: 'GET' })
.inputValidator((data: { id: string }) => data)
.handler(async ({ data }) => findUserById(data.id))
Request Middleware
import { createMiddleware } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware().server(async ({ next, context, request }) => {
console.log('Request:', request.url)
const result = await next()
return result
})
Server Function Middleware
import { createMiddleware } from '@tanstack/react-start'
const authMiddleware = createMiddleware({ type: 'function' })
.client(async ({ next }) => {
return next({
sendContext: { token: getToken() },
})
})
.server(async ({ next, context }) => {
if (!context.token) throw new Error('Unauthorized')
return next()
})
export const getSecret = createServerFn()
.middleware([authMiddleware])
.handler(async () => 'secret data')
Global Middleware
import { createStart, createMiddleware } from '@tanstack/react-start'
const globalAuthMiddleware = createMiddleware().server(async ({ next }) => {
return next()
})
export const startInstance = createStart(() => ({
requestMiddleware: [globalAuthMiddleware],
functionMiddleware: [someOtherMiddleware],
}))
Vite Configuration
import { defineConfig } from 'vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import viteReact from '@vitejs/plugin-react'
export default defineConfig({
server: { port: 3000 },
resolve: { tsconfigPaths: true },
plugins: [
tanstackStart(),
viteReact(),
],
})
TypeScript Configuration
{
"compilerOptions": {
"jsx": "react-jsx",
"moduleResolution": "Bundler",
"module": "ESNext",
"target": "ES2022",
"skipLibCheck": true,
"strictNullChecks": true
}
}
Note: Avoid verbatimModuleSyntax as it can leak server bundles into client bundles.
Deployment: Cloudflare Workers
pnpm add -D @cloudflare/vite-plugin wrangler
import { defineConfig } from 'vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import { cloudflare } from '@cloudflare/vite-plugin'
import viteReact from '@vitejs/plugin-react'
export default defineConfig({
plugins: [
cloudflare({ viteEnvironment: { name: 'ssr' } }),
tanstackStart(),
viteReact(),
],
})
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "tanstack-start-app",
"compatibility_date": "2025-09-02",
"compatibility_flags": ["nodejs_compat"],
"main": "@tanstack/react-start/server-entry"
}
Deployment: Netlify
pnpm add -D @netlify/vite-plugin-tanstack-start
import { defineConfig } from 'vite'
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import netlify from '@netlify/vite-plugin-tanstack-start'
import viteReact from '@vitejs/plugin-react'
export default defineConfig({
plugins: [tanstackStart(), netlify(), viteReact()],
})
Deployment: Nitro (Vercel, Node, Bun, etc.)
npm install nitro
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
import { defineConfig } from 'vite'
import { nitro } from 'nitro/vite'
import viteReact from '@vitejs/plugin-react'
export default defineConfig({
plugins: [tanstackStart(), nitro(), viteReact()],
})
For Node.js deployment:
{
"scripts": {
"build": "vite build",
"start": "node .output/server/index.mjs"
}
}
Zod Validation with Server Functions
import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'
const UserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
})
export const createUser = createServerFn({ method: 'POST' })
.inputValidator(UserSchema)
.handler(async ({ data }) => {
return db.insert(users).values(data)
})
Server Function with FormData
export const submitForm = createServerFn({ method: 'POST' })
.inputValidator((data) => {
if (!(data instanceof FormData)) {
throw new Error('Expected FormData')
}
return {
name: data.get('name')?.toString() || '',
email: data.get('email')?.toString() || '',
}
})
.handler(async ({ data }) => {
return { success: true, name: data.name }
})
Server Context Utilities
Import from @tanstack/react-start/server:
import {
getRequest,
getRequestHeader,
setResponseHeader,
setResponseHeaders,
setResponseStatus,
} from '@tanstack/react-start/server'
Example usage:
import { createServerFn } from '@tanstack/react-start'
import {
getRequest,
getRequestHeader,
setResponseHeaders,
setResponseStatus,
} from '@tanstack/react-start/server'
export const getCachedData = createServerFn({ method: 'GET' }).handler(async () => {
const request = getRequest()
const authHeader = getRequestHeader('Authorization')
setResponseHeaders(new Headers({
'Cache-Control': 'public, max-age=300',
'CDN-Cache-Control': 'max-age=3600, stale-while-revalidate=600',
}))
setResponseStatus(200)
return fetchData()
})
Error Handling in Server Functions
Errors, redirects, and not-found responses are serialized to the client automatically.
import { createServerFn } from '@tanstack/react-start'
import { redirect, notFound } from '@tanstack/react-router'
export const riskyFunction = createServerFn().handler(async () => {
throw new Error('Something went wrong!')
})
try {
await riskyFunction()
} catch (error) {
console.log(error.message)
}
export const requireAuth = createServerFn().handler(async () => {
const user = await getCurrentUser()
if (!user) {
throw redirect({ to: '/login' })
}
return user
})
export const getPost = createServerFn()
.inputValidator((data: { id: string }) => data)
.handler(async ({ data }) => {
const post = await db.findPost(data.id)
if (!post) {
throw notFound()
}
return post
})
Server Routes (API Routes)
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/api/posts')({
server: {
handlers: {
GET: async () => {
const posts = await db.query.posts.findMany()
return Response.json(posts)
},
POST: async ({ request }) => {
const body = await request.json()
const post = await db.insert(posts).values(body)
return Response.json(post, { status: 201 })
},
},
},
})
Common Package Manager Commands
npx @tanstack/cli@latest create
npx gitpick TanStack/router/tree/main/examples/react/start-basic start-basic
npm i @tanstack/react-start @tanstack/react-router react react-dom
npm i -D vite @vitejs/plugin-react typescript @types/react @types/react-dom @types/node
npm run dev
npm run build