Server Functions
Server functions run on the server but can be called from anywhere — loaders, components, event handlers. On the client they become RPC calls automatically.
Basic GET server function
createServerFn({ method: 'GET' }).handler(async () => { ... })The simplest pattern. Define a handler, call it from anywhere. During SSR it runs directly. From the client it becomes a fetch request.
2026-02-20T10:01:43.136Z9401First load came from the loader (SSR). Clicking calls it client-side via RPC.
POST with input validation
.inputValidator((data) => { ... }).handler(async ({ data }) => { ... })Input validators run before the handler. They validate and transform the data that crosses the network boundary. Use plain functions or Zod schemas.
const addNumbers = createServerFn({ method: 'POST' })
.inputValidator((data: { a: number; b: number }) => {
if (typeof data.a !== 'number') throw new Error('...')
return data
})
.handler(async ({ data }) => {
return { result: data.a + data.b }
})Request context
getRequest(), getRequestHeader(), setResponseHeaders()Inside a handler you can access the incoming request, read headers, and set response headers. Useful for auth checks, caching, and logging.
GET/server-fnsclaude-start-cf.jmorrison.workers.devMozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko;...On SSR the request comes from the initial page load. On re-fetch it comes from the client RPC call — notice the path changes.
Error handling
throw new Error() / throw redirect() / throw notFound()Errors thrown in server functions are serialized to the client. You can also throw redirect() for navigation or notFound() for 404s.
// Errors
throw new Error('Something went wrong')
// Redirects (auth, navigation)
throw redirect({ to: '/login' })
// 404s (missing resources)
throw notFound()FormData handling
.inputValidator((data) => { /* parse FormData */ })Server functions can accept FormData directly. The input validator parses it into a typed object before the handler runs. Works with progressive enhancement — the form can submit without JS too.
useServerFn hook
const fn = useServerFn(myServerFn)The useServerFn hook wraps a server function for use in components. It handles pending states and integrates with React's transition model.
This calls getGreeting from ~/utils/greetings.functions.ts, which imports server-only logic from greetings.server.ts.
File organization
*.functions.ts / *.server.ts / *.tsRecommended pattern for organizing server-side code. Separates RPC boundaries from internal server logic.
src/utils/ ├── greetings.functions.ts # createServerFn wrappers (import anywhere) ├── greetings.server.ts # Server-only helpers (DB, internal logic) └── schemas.ts # Shared types & validation (client-safe)
// Only import inside server function handlers
export function buildGreeting(name: string) {
const greeting = greetings[Math.floor(Math.random() * greetings.length)]
return `${greeting}, ${name}!`
}import { createServerFn } from '@tanstack/react-start'
import { buildGreeting } from './greetings.server'
export const getGreeting = createServerFn({ method: 'GET' })
.inputValidator((data: { name: string }) => data)
.handler(async ({ data }) => {
return { message: buildGreeting(data.name) }
})// ✅ Static import — build replaces with RPC stub
import { getGreeting } from '~/utils/greetings.functions'
const result = await getGreeting({ data: { name: 'World' } })Key rule: *.functions.ts files can be imported anywhere. *.server.ts files should only be imported inside server function handlers — the build process tree-shakes them from the client bundle.