TanStack StartError HandlingTypeScriptServer

TanStack Start: Server Function Error Handling Patterns

Handle errors gracefully in TanStack Start server functions with Zod validation, error mapping, typed error responses, and client-side error recovery.

Sam Rivera
Sam Rivera
June 1, 202610 min read

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:

tsx
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:

tsx
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 TypeWhenUser ImpactRecovery
ValidationInvalid form inputField-level errorFix input
Not foundResource missing404 pageNavigate away
AuthSession expiredRedirect to loginRe-authenticate
Rate limitToo many requestsWait messageRetry after
ServerInternal errorRetry promptAuto-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.