TanStack / TanStack/router

Middleware error handling: returning custom values from catch blocks doesn't reach the client

Open
#7,238 2 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
15.1k
Forks
1.9k
Avg merge
1d 20h
Merged PRs (30d)
143

Description

Problem

We use function middleware to catch errors and convert them to structured error responses so the client can branch on error codes (show field-level validation errors, redirect on auth failures, show billing-specific messages, etc.).

export const errorHandler = createMiddleware({ type: 'function' }).server(async ({ next }) => {
  try {
    return await next()
  } catch (error) {
    if (error instanceof DomainError) {
      return { success: false, error: { code: 'BILLING_ERROR', message: error.message } }
    }
    return { success: false, error: { code: 'INTERNAL_SERVER_ERROR', message: 'Something went wrong' } }
  }
})

Registered globally:

createStart(() => ({
  functionMiddleware: [errorHandler],
}))

Expected: Client receives { success: false, error: { code: 'BILLING_ERROR', ... } } as the resolved value.

Actual: Client promise rejects with just the inner error object { code: 'BILLING_ERROR', ... }. The { success: false } wrapper is stripped.

Root cause

executeMiddleware uses { result, error } internally (ServerFnMiddlewareResult). When middleware returns an object with an .error property, line 140 of createServerFn.ts treats it as a middleware-chain error and throws it:

if (result.error) throw result.error

Any return value from middleware that has a truthy .error field collides with this internal protocol. The middleware's return is never delivered to the client as a resolved value.

What we need

We want a global error boundary for server functions — similar to FastAPI exception handlers or Express error middleware. The goal is:

  1. Classified errors (auth, billing, validation) — send a structured error code to the client so it can react (redirect, show field errors, show billing prompts)
  2. Unexpected errors — log server-side for debugging, send a generic "Something went wrong" to the client
  3. Single boundary — handlers just throw, middleware handles classification

Questions

  1. Is returning custom values from middleware catch blocks intended to work? The docs only show return next() or throw from middleware — never custom returns.
  2. If not, what's the recommended pattern for global error classification in server functions? Should middleware throw a structured error object and let the client catch the rejection?
  3. Would TanStack consider documenting an official error-handling middleware pattern?

Environment

  • @tanstack/react-start v1.167.17
  • @tanstack/start-client-core v1.167.17

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with createServerFn.ts around line 140 and trace executeMiddleware and ServerFnMiddlewareResult to reproduce the collision when a middleware return value has an error property. Check the existing middleware documentation and tests for the intended return and rejection contract. Done means the behavior is defined and the relevant implementation or documentation covers global error handling.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, backend-api-design
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.