Critical rules: a server function is an independent HTTP endpoint reachable directly, so a route's beforeLoad redirect protects the UI only — auth must live inside the handler or its middleware for every function touching private data; never use Next.js/Remix patterns ("use server", getServerSideProps, Remix loader/action); loaders are isomorphic, so DB/secrets/filesystem access belongs in a server function, never a loader; useServerFn is mandatory when a function throws redirect() or notFound() (optional otherwise); and strict serialization is on by default (FormData allowed as POST input, Response as output).
Includes templates for a full CRUD module and for FormData submissions with Zod validation.
Do NOT use this skill for raw external/public HTTP endpoints (use start-server-routes), for composable middleware chains (use start-middleware), or for Next.js/Remix "use server" patterns.
TanStack Start Server Functions
Server functions are same-origin, type-safe RPC endpoints created with
createServerFn. They run only on the server but are callable from loaders,
components, hooks, and other server functions. This skill targets
@tanstack/react-start v1.166.2.
Agent Workflow (MANDATORY)
Before implementing, verify current APIs against Context7
(/websites/tanstack_start_framework_react) + Exa, then explore the target
codebase. After changes, run fuse-ai-pilot:sniper.
Critical Rules (read first)
- A server function is an independent HTTP endpoint. A route
beforeLoadredirect protects the route UI, NOT the RPC — an attacker can hit the endpoint directly. Put auth inside the.handler()or in middleware for every function touching private data. - Never use Next.js/Remix patterns. No
"use server",getServerSideProps, Remixloader/action, orreact-router-dom. UsecreateServerFnexclusively (from@tanstack/react-start). - Loaders are isomorphic (run on client AND server). DB queries, secrets, and file-system access MUST live in a server function, never in a loader.
useServerFnis MANDATORY when the function doesthrow redirect()orthrow notFound()— the hook wires the throw into the router. Optional for plain-data functions (a direct call oruseMutation/useQueryis fine).- Strict serialization is on by default. Validator input and handler return
types must be serializable (
FormDataallowed as POST input,Responseallowed as output).strict: falsedisables only the TS check, not runtime.
Overview
| When to Use | Do NOT Use |
|---|---|
| Internal type-safe RPC from your own app | Public/cross-origin API (use start-server-routes) |
| Data fetching in loaders | Raw HTTP method routing on a URL path |
| Mutations from event handlers | Composable auth/logging chains (use start-middleware) |
Architecture
src/utils/
├── users.functions.ts # createServerFn wrappers — safe to import anywhere
├── users.server.ts # Server-only helpers (DB queries, secrets)
└── schemas.ts # Shared Zod schemas — client-safe
The build replaces server function bodies with RPC stubs in the client bundle,
so static imports of .functions.ts from client components are safe.
→ See crud-server-functions.md
Reference Guide
Concepts
| Topic | Reference | Load when |
|---|---|---|
| Creating functions | creating.md | Defining createServerFn, validators, serialization |
| Calling functions | calling.md | Invoking from loaders/components, useServerFn, redirect/notFound |
| Security | security.md | Enforcing auth, CSRF, caching auth'd responses |
Templates
| Template | When to Use |
|---|---|
| crud-server-functions.md | Building a full CRUD module |
| form-with-validation.md | Handling FormData submissions with Zod |
Quick Reference
Basic function with validation
import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'
export const createUser = createServerFn({ method: 'POST' })
.validator(z.object({ name: z.string().min(1) }))
.handler(async ({ data }) => db.users.create(data))
await createUser({ data: { name: 'John' } })
Redirect (requires useServerFn in components)
import { useServerFn } from '@tanstack/react-start'
import { redirect } from '@tanstack/react-router'
const signup = createServerFn({ method: 'POST' })
.handler(async () => { throw redirect({ to: '/dashboard' }) })
// In a component:
const signupFn = useServerFn(signup)
→ See calling.md for the full redirect/notFound rules
Best Practices
DO
- Enforce auth inside every handler that reads/writes private data
- Split server-only helpers into
.server.ts, wrappers into.functions.ts - Validate every input crossing the network boundary with Zod
- Wrap with
useServerFnwhen in doubt (no-op for plain-data functions)
DON'T
- Rely on route guards to protect a server function
- Use
"use server",getServerSideProps, or Remixaction - Put DB queries or secrets in a route loader
- Dynamically
import()server functions (breaks bundler shaking)