TL;DR: Server functions in TanStack Start need structured error handling. This guide covers Zod validation error mapping, typed error responses that preserve type safety across the network boundary, error grouping for monitoring, and client-side recovery patterns.
Introduction
TanStack Start server functions bridge the client and server. When an error occurs on the server, it needs to propagate to the client in a structured way. The challenge is maintaining type safety across the network boundary while providing useful error information.
Typed Error Responses
To maintain type safety across the client-server boundary, define a discriminated union for your error types. Here's how to create a typed error system that covers validation, auth, not-found, and server errors:
import { createServerFn } from '@tanstack/react-start'
import { z } from 'zod'
type ApiError =
| { type: 'validation'; fields: Record<string, string[]> }
| { type: 'not_found'; resource: string }
| { type: 'auth'; message: string }
| { type: 'server'; retryAfter: number }
export const updateProfileFn = createServerFn({ method: 'POST' })
.validator(z.object({
name: z.string().min(2),
email: z.string().email(),
}))
.handler(async ({ data }): Promise<{ success: true } | ApiError> => {
try {
await db.updateUser(data)
return { success: true }
} catch (error) {
return { type: 'server', retryAfter: 30 }
}
})
The return type { success: true } | ApiError is fully type-checked. On the client side, TypeScript forces you to handle every case, preventing unhandled error paths in production.
Client-Side Error Handling
On the client side, the mutation handler checks whether the result is an error or a success. Using a type-narrowing pattern keeps the error handling branch clear and maintainable:
const mutation = useMutation({
mutationFn: updateProfileFn,
onSuccess: (result) => {
if ('type' in result) {
// Handle structured error
if (result.type === 'validation') {
// Set field errors
}
if (result.type === 'auth') {
router.navigate({ to: '/login' })
}
return
}
// Success
toast.success('Profile updated')
},
})
The client-side code handles each error type with appropriate UX: validation errors show inline field messages, auth errors redirect to login, and server errors trigger retry flows — all while preserving full type safety.
Error Type Decision Guide
| Error Type | When | User Impact | Recovery |
|---|---|---|---|
| Validation | Invalid form input | Field-level error | Fix input |
| Not found | Resource missing | 404 page | Navigate away |
| Auth | Session expired | Redirect to login | Re-authenticate |
| Rate limit | Too many requests | Wait message | Retry after |
| Server | Internal error | Retry prompt | Auto-retry |
Conclusion
Typed error responses in TanStack Start server functions give you type-safe error handling across the network boundary. The pattern transforms cryptic server crashes into actionable client-side recovery flows. For comprehensive error handling, combine this approach with Error Boundaries for route-level error UI, Middleware and Request Lifecycle for centralized error logging, and SaaS Monitoring and Observability for production error tracking and alerting.