Documentation Index
Fetch the complete documentation index at: https://mintlify.com/TanStack/router/llms.txt
Use this file to discover all available pages before exploring further.
Server Functions
Server functions are the core primitive for executing code on the server in TanStack Start. They provide a type-safe way to define server-side logic that can be called from your React components.
Creating Server Functions
Use createServerFn to define a server function:
import { createServerFn } from '@tanstack/react-start'
const getUser = createServerFn({ method: 'GET' })
.handler(async () => {
// This code runs on the server
const user = await db.user.findFirst()
return user
})
Basic Usage
GET Requests
For simple data fetching, use GET requests:
import { createServerFn } from '@tanstack/react-start'
import { createFileRoute } from '@tanstack/react-router'
const fetchUser = createServerFn({ method: 'GET' }).handler(async () => {
const user = await db.user.findFirst()
return { name: user.name, email: user.email }
})
export const Route = createFileRoute('/profile')({
loader: async () => {
const user = await fetchUser()
return { user }
},
})
POST Requests
For mutations and data that requires validation, use POST requests:
import { createServerFn } from '@tanstack/react-start'
const updateUser = createServerFn({ method: 'POST' })
.inputValidator((data: { name: string; email: string }) => data)
.handler(async ({ data }) => {
await db.user.update({
where: { id: 1 },
data: { name: data.name, email: data.email },
})
return { success: true }
})
Validate input data before it reaches your handler:
import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'
const userSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().min(18),
})
const createUser = createServerFn({ method: 'POST' })
.inputValidator((data: unknown) => userSchema.parse(data))
.handler(async ({ data }) => {
// data is fully typed and validated
const user = await db.user.create({ data })
return user
})
Using Validation Adapters
TanStack Start provides adapters for popular validation libraries:
import { createServerFn } from '@tanstack/react-start'
import { zodValidator } from '@tanstack/zod-adapter'
import { z } from 'zod'
const schema = z.object({
email: z.string().email(),
password: z.string().min(8),
})
const login = createServerFn({ method: 'POST' })
.inputValidator(zodValidator(schema))
.handler(async ({ data }) => {
// data is typed as { email: string, password: string }
return authenticateUser(data)
})
Server Function Context
Access request metadata and shared context in your handlers:
import { createServerFn } from '@tanstack/react-start'
const getPost = createServerFn({ method: 'GET' })
.inputValidator((id: string) => id)
.handler(async ({ data, context, method, serverFnMeta }) => {
// data: validated input
// context: shared context from middleware
// method: HTTP method ('GET' or 'POST')
// serverFnMeta: { id, name, filename }
const post = await db.post.findUnique({ where: { id: data } })
return post
})
Calling Server Functions
From Loaders
The most common pattern is calling server functions from route loaders:
import { createFileRoute } from '@tanstack/react-router'
import { fetchPost } from '~/utils/posts'
export const Route = createFileRoute('/posts/$postId')({
loader: async ({ params }) => {
const post = await fetchPost({ data: params.postId })
return { post }
},
})
From Components with useServerFn
For client-side calls (mutations, refetching), use useServerFn:
import { useServerFn } from '@tanstack/react-start'
import { updateUser } from '~/utils/users'
function ProfileEditor() {
const updateUserFn = useServerFn(updateUser)
const handleSave = async () => {
const result = await updateUserFn({
data: { name: 'John', email: 'john@example.com' }
})
console.log(result)
}
return <button onClick={handleSave}>Save</button>
}
Direct Calls
You can also call server functions directly:
import { fetchUser } from '~/utils/users'
// In a loader or another server function
const user = await fetchUser()
// In a component (client-side)
const user = await fetchUser({
data: userId,
headers: { 'x-custom': 'value' },
signal: abortController.signal,
})
Middleware
Add middleware to server functions for cross-cutting concerns:
import { createServerFn, createMiddleware } from '@tanstack/react-start'
const authMiddleware = createMiddleware({ type: 'function' })
.server(async ({ next, context }) => {
const session = await getSession(context.request)
if (!session) {
throw new Error('Unauthorized')
}
return next({ context: { user: session.user } })
})
const getProfile = createServerFn({ method: 'GET' })
.middleware([authMiddleware])
.handler(async ({ context }) => {
// context.user is available from middleware
return { name: context.user.name }
})
Error Handling
Server functions propagate errors to the client:
import { createServerFn } from '@tanstack/react-start'
import { notFound } from '@tanstack/react-router'
const getPost = createServerFn({ method: 'GET' })
.inputValidator((id: string) => id)
.handler(async ({ data }) => {
const post = await db.post.findUnique({ where: { id: data } })
if (!post) {
throw notFound() // Special router error
}
return post
})
Redirects
Redirect users from server functions:
import { createServerFn } from '@tanstack/react-start'
import { redirect } from '@tanstack/react-router'
const logout = createServerFn({ method: 'POST' }).handler(async () => {
await clearSession()
throw redirect({ to: '/login' })
})
Streaming Responses
Stream data back to the client using RawStream:
import { createServerFn, RawStream } from '@tanstack/react-start'
const streamLogs = createServerFn({ method: 'GET' }).handler(async () => {
return new RawStream(async (controller) => {
for (let i = 0; i < 10; i++) {
controller.send(`Log ${i}\n`)
await new Promise(r => setTimeout(r, 100))
}
controller.end()
})
})
Server-Only Imports
Mark modules as server-only to prevent them from being bundled for the client:
import '@tanstack/react-start/server-only'
import { createServerFn } from '@tanstack/react-start'
import { db } from '~/db' // This will never be sent to the client
const getUsers = createServerFn({ method: 'GET' }).handler(async () => {
return db.user.findMany()
})
Client-Only Imports
Mark modules as client-only:
import '@tanstack/react-start/client-only'
// This module will error if imported on the server
Type Safety
Server functions are fully type-safe:
import { createServerFn } from '@tanstack/react-start'
const getUser = createServerFn({ method: 'GET' })
.inputValidator((id: string) => id)
.handler(async ({ data }) => {
return { id: data, name: 'John', age: 30 }
})
// TypeScript knows the return type
const user = await getUser({ data: '123' })
// user: { id: string, name: string, age: number }
Best Practices
Co-locate with Features
Keep server functions close to where they’re used:
src/
routes/
posts/
index.tsx
utils/
posts.ts # Server functions for posts
users.ts # Server functions for users
Reuse Across Routes
Define server functions once and import them across multiple routes:
// utils/posts.ts
export const fetchPosts = createServerFn({ method: 'GET' }).handler(/*..*/)
export const fetchPost = createServerFn({ method: 'GET' }).handler(/*..*/)
// routes/posts/index.tsx
import { fetchPosts } from '~/utils/posts'
// routes/posts/$postId.tsx
import { fetchPost } from '~/utils/posts'
Always validate POST request data:
const createPost = createServerFn({ method: 'POST' })
.inputValidator(zodValidator(postSchema)) // ✅ Good
.handler(async ({ data }) => { /*...*/ })
const badCreatePost = createServerFn({ method: 'POST' })
.handler(async ({ data }) => { // ❌ Bad - no validation
// data is unknown
})
Handle Errors Gracefully
Provide meaningful error messages:
const updateProfile = createServerFn({ method: 'POST' })
.inputValidator(profileSchema)
.handler(async ({ data }) => {
try {
return await db.user.update({ where: { id: data.id }, data })
} catch (error) {
if (error.code === 'P2025') {
throw new Error('User not found')
}
throw new Error('Failed to update profile')
}
})
API Reference
createServerFn(options?)
Creates a new server function builder.
Parameters:
options.method: 'GET' | 'POST' - HTTP method (default: 'GET')
Returns: ServerFnBuilder
ServerFnBuilder Methods
Adds input validation to the server function.
Parameters:
validator: Function or adapter that validates input data
Returns: ServerFnAfterValidator
.middleware(middlewares)
Adds middleware to the server function.
Parameters:
middlewares: Array of middleware functions
Returns: ServerFnAfterMiddleware
.handler(fn)
Defines the server function implementation.
Parameters:
fn: (ctx: ServerFnCtx) => Promise<T> - Handler function
Returns: Fetcher<T> - Callable server function
ServerFnCtx
Context object passed to handler functions:
interface ServerFnCtx {
data: unknown // Validated input data
context: Record<string, any> // Shared context from middleware
method: 'GET' | 'POST' // HTTP method
serverFnMeta: {
id: string // Unique function ID
name: string // Function variable name
filename: string // Source file path
}
}