Critical rules enforced: the method chain order is fixed (.middleware() → .validator() → .client() → .server()); shape-validated sendContext data is NOT authorization — always re-check access against the server-trusted session before using a client-sent value as a query key, filter, or path param; client context is never sent to the server unless explicitly opted in, and the client can lie about anything it sends; .client() runs on the server during SSR, so browser-only APIs need a typeof window guard; and staticFunctionMiddleware must always be last in the chain.
Includes templates for an auth + permission-based authorization factory and for client-side middleware (headers, custom fetch, telemetry).
Do NOT use this skill for defining the RPC itself (use start-server-functions), raw HTTP endpoints (use start-server-routes), or route-level UX guards (use router beforeLoad).
TanStack Start Middleware
Middleware customizes the behavior of server functions and server routes. It is
composable — one middleware can depend on others to form an ordered chain.
Import createMiddleware from @tanstack/react-start. 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)
- TypeScript enforces method order:
.middleware()→.validator()→.client()→.server(). Any other order is a type error. - Shape validation is NOT authorization. A parsed UUID from
sendContextis a well-formed identifier, not an authorized one. Always re-check access against the session principal before using a client-sent value as a query key, filter, or path param. - Client context is not sent to the server by default. You must opt in with
next({ sendContext }). Anything the client sends, the client can lie about — derive the session from a server-trusted source (cookie + DB), never fromsendContext. .client()runs on the server during SSR. Guard browser-only APIs (localStorage,window) withtypeof window !== 'undefined'.staticFunctionMiddlewaremust be LAST in the chain (from@tanstack/start-static-server-functions, experimental).
Overview
| Feature | Request Middleware | Server Function Middleware |
|---|---|---|
| Created with | createMiddleware() |
createMiddleware({ type: 'function' }) |
| Scope | All server requests (SSR, routes, functions) | Server functions only |
| Methods | .server() |
.client(), .server() |
| Input validation | No | Yes (.validator()) |
| Client-side logic | No | Yes |
Request middleware cannot depend on server function middleware; server function middleware can depend on both.
Architecture
src/
├── start.ts # createStart → global request/function middleware
├── middleware/
│ ├── auth.ts # authMiddleware (request) + authorization factory
│ └── logging.ts # request logging
→ See auth-authorization.md
Reference Guide
Concepts
| Topic | Reference | Load when |
|---|---|---|
| Types & chaining | types.md | Choosing request vs function middleware, attaching to fns/routes |
| Context passing | context.md | Sharing data via next(), sendContext client↔server, headers/fetch |
| Global & static | global.md | Global middleware, execution order, staticFunctionMiddleware |
Templates
| Template | When to Use |
|---|---|
| auth-authorization.md | Auth + permission-based authorization factory |
| client-middleware.md | Client-side headers, custom fetch, telemetry |
Quick Reference
Request middleware
import { createMiddleware } from '@tanstack/react-start'
const logging = createMiddleware().server(async ({ next, request }) => {
console.log(request.url)
return next()
})
Server function middleware with context
const auth = createMiddleware().server(async ({ next, request }) => {
const session = await getSession(request.headers)
if (!session) throw new Error('Unauthorized')
return next({ context: { session } }) // typed downstream
})
const fn = createServerFn().middleware([auth]).handler(async ({ context }) =>
db.orders.findMany({ where: { userId: context.session.userId } }),
)
→ See context.md for sendContext and header merging
Best Practices
DO
- Load the session in a request middleware from a server-trusted cookie
- Re-check access (membership/role) before trusting any client-sent id
- Use a factory for parameterized authorization (permissions per function)
- Keep method chains in the enforced order
DON'T
- Trust
sendContextshape as authorization - Call
localStorage/windowin.client()without an SSR guard - Place
staticFunctionMiddlewareanywhere but last