middleware
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMiddleware
中间件
Middleware customizes the behavior of server functions and server routes. It is composable — middleware can depend on other middleware to form a chain.
CRITICAL: TypeScript enforces method order:→middleware()→validator()→client(). Wrong order causes type errors. CRITICAL: Validating the shape ofserver()(e.g.sendContext) is NOT authorization. A parsed identifier is a well-formed identifier, not an authorized one. Always re-check access against the session principal before using a client-sent ID as a query key, filter, or path parameter.z.string().uuid().parse(...)
中间件用于自定义服务端函数和服务端路由的行为。它是可组合的——中间件可以依赖其他中间件形成调用链。
关键警告:TypeScript 会强制方法调用顺序:→middleware()→validator()→client()。顺序错误会导致类型错误。 关键警告:验证server()的结构(例如sendContext)并不等同于授权。解析后的标识符只是格式正确的标识符,并不代表已授权。在将客户端发送的 ID 用作查询键、过滤器或路径参数之前,始终要根据会话主体重新检查访问权限。z.string().uuid().parse(...)
Two Types of Middleware
两种中间件类型
| Feature | Request Middleware | Server Function Middleware |
|---|---|---|
| Scope | All server requests (SSR, routes, functions) | Server functions only |
| Methods | | |
| Input validation | No | Yes ( |
| Client-side logic | No | Yes |
| Created with | | |
Request middleware cannot depend on server function middleware. Server function middleware can depend on both types.
| 特性 | 请求中间件 | 服务端函数中间件 |
|---|---|---|
| 适用范围 | 所有服务端请求(SSR、路由、函数) | 仅服务端函数 |
| 可用方法 | | |
| 输入验证 | 不支持 | 支持( |
| 客户端逻辑 | 不支持 | 支持 |
| 创建方式 | | |
请求中间件不能依赖服务端函数中间件。服务端函数中间件可以同时依赖两种类型的中间件。
Request Middleware
请求中间件
Runs on ALL server requests (SSR, server routes, server functions):
tsx
// Use @tanstack/<framework>-start for your framework (react, solid, vue)
import { createMiddleware } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware().server(
async ({ next, context, request }) => {
console.log('Request:', request.url)
const result = await next()
return result
},
)在所有服务端请求上运行(SSR、服务端路由、服务端函数):
tsx
// Use @tanstack/<framework>-start for your framework (react, solid, vue)
import { createMiddleware } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware().server(
async ({ next, context, request }) => {
console.log('Request:', request.url)
const result = await next()
return result
},
)Server Function Middleware
服务端函数中间件
Has both client and server phases:
tsx
// Use @tanstack/<framework>-start for your framework (react, solid, vue)
import { createMiddleware } from '@tanstack/react-start'
const authMiddleware = createMiddleware({ type: 'function' })
.client(async ({ next }) => {
// Runs on client BEFORE the RPC call
const result = await next()
// Runs on client AFTER the RPC response
return result
})
.server(async ({ next, context }) => {
// Runs on server BEFORE the handler
const result = await next()
// Runs on server AFTER the handler
return result
})同时包含客户端和服务端阶段:
tsx
// Use @tanstack/<framework>-start for your framework (react, solid, vue)
import { createMiddleware } from '@tanstack/react-start'
const authMiddleware = createMiddleware({ type: 'function' })
.client(async ({ next }) => {
// Runs on client BEFORE the RPC call
const result = await next()
// Runs on client AFTER the RPC response
return result
})
.server(async ({ next, context }) => {
// Runs on server BEFORE the handler
const result = await next()
// Runs on server AFTER the handler
return result
})Attaching Middleware to Server Functions
为服务端函数挂载中间件
tsx
// Use @tanstack/<framework>-start for your framework (react, solid, vue)
import { createServerFn } from '@tanstack/react-start'
const fn = createServerFn()
.middleware([authMiddleware])
.handler(async ({ context }) => {
// context contains data from middleware
return { user: context.user }
})tsx
// Use @tanstack/<framework>-start for your framework (react, solid, vue)
import { createServerFn } from '@tanstack/react-start'
const fn = createServerFn()
.middleware([authMiddleware])
.handler(async ({ context }) => {
// context contains data from middleware
return { user: context.user }
})Context Passing via next()
通过 next() 传递上下文
Pass context down the middleware chain:
tsx
const authMiddleware = createMiddleware().server(async ({ next, request }) => {
const session = await getSession(request.headers)
if (!session) throw new Error('Unauthorized')
return next({
context: { session },
})
})
const roleMiddleware = createMiddleware()
.middleware([authMiddleware])
.server(async ({ next, context }) => {
console.log('Session:', context.session) // typed!
return next()
})在中间件链中向下传递上下文:
tsx
const authMiddleware = createMiddleware().server(async ({ next, request }) => {
const session = await getSession(request.headers)
if (!session) throw new Error('Unauthorized')
return next({
context: { session },
})
})
const roleMiddleware = createMiddleware()
.middleware([authMiddleware])
.server(async ({ next, context }) => {
console.log('Session:', context.session) // typed!
return next()
})Sending Context Between Client and Server
在客户端与服务端之间传递上下文
Client → Server (sendContext)
客户端 → 服务端(sendContext)
tsx
const workspaceMiddleware = createMiddleware({ type: 'function' })
.client(async ({ next, context }) => {
return next({
sendContext: {
workspaceId: context.workspaceId,
},
})
})
.server(async ({ next, context }) => {
// workspaceId available here, but VALIDATE IT
console.log('Workspace:', context.workspaceId)
return next()
})tsx
const workspaceMiddleware = createMiddleware({ type: 'function' })
.client(async ({ next, context }) => {
return next({
sendContext: {
workspaceId: context.workspaceId,
},
})
})
.server(async ({ next, context }) => {
// workspaceId available here, but VALIDATE IT
console.log('Workspace:', context.workspaceId)
return next()
})Server → Client (sendContext in server)
服务端 → 客户端(服务端中的 sendContext)
tsx
const serverTimer = createMiddleware({ type: 'function' }).server(
async ({ next }) => {
return next({
sendContext: {
timeFromServer: new Date(),
},
})
},
)
const clientLogger = createMiddleware({ type: 'function' })
.middleware([serverTimer])
.client(async ({ next }) => {
const result = await next()
console.log('Server time:', result.context.timeFromServer)
return result
})tsx
const serverTimer = createMiddleware({ type: 'function' }).server(
async ({ next }) => {
return next({
sendContext: {
timeFromServer: new Date(),
},
})
},
)
const clientLogger = createMiddleware({ type: 'function' })
.middleware([serverTimer])
.client(async ({ next }) => {
const result = await next()
console.log('Server time:', result.context.timeFromServer)
return result
})Input Validation in Middleware
中间件中的输入验证
tsx
import { z } from 'zod'
import { zodValidator } from '@tanstack/zod-adapter'
const workspaceMiddleware = createMiddleware({ type: 'function' })
.validator(zodValidator(z.object({ workspaceId: z.string() })))
.server(async ({ next, data }) => {
console.log('Workspace:', data.workspaceId)
return next()
})tsx
import { z } from 'zod'
import { zodValidator } from '@tanstack/zod-adapter'
const workspaceMiddleware = createMiddleware({ type: 'function' })
.validator(zodValidator(z.object({ workspaceId: z.string() })))
.server(async ({ next, data }) => {
console.log('Workspace:', data.workspaceId)
return next()
})Global Middleware
全局中间件
Create to configure global middleware:
src/start.tstsx
// src/start.ts
// Use @tanstack/<framework>-start for your framework (react, solid, vue)
import { createStart, createMiddleware } from '@tanstack/react-start'
const requestLogger = createMiddleware().server(async ({ next, request }) => {
console.log(`${request.method} ${request.url}`)
return next()
})
const functionAuth = createMiddleware({ type: 'function' }).server(
async ({ next }) => {
// runs for every server function
return next()
},
)
export const startInstance = createStart(() => ({
requestMiddleware: [requestLogger],
functionMiddleware: [functionAuth],
}))创建 文件来配置全局中间件:
src/start.tstsx
// src/start.ts
// Use @tanstack/<framework>-start for your framework (react, solid, vue)
import { createStart, createMiddleware } from '@tanstack/react-start'
const requestLogger = createMiddleware().server(async ({ next, request }) => {
console.log(`${request.method} ${request.url}`)
return next()
})
const functionAuth = createMiddleware({ type: 'function' }).server(
async ({ next }) => {
// runs for every server function
return next()
},
)
export const startInstance = createStart(() => ({
requestMiddleware: [requestLogger],
functionMiddleware: [functionAuth],
}))Using Middleware with Server Routes
在服务端路由中使用中间件
All handlers in a route
路由中的所有处理函数
tsx
export const Route = createFileRoute('/api/users')({
server: {
middleware: [authMiddleware],
handlers: {
GET: async ({ context }) => Response.json(context.user),
POST: async ({ request }) => {
/* ... */
},
},
},
})tsx
export const Route = createFileRoute('/api/users')({
server: {
middleware: [authMiddleware],
handlers: {
GET: async ({ context }) => Response.json(context.user),
POST: async ({ request }) => {
/* ... */
},
},
},
})Specific handlers only
仅特定处理函数
tsx
export const Route = createFileRoute('/api/users')({
server: {
handlers: ({ createHandlers }) =>
createHandlers({
GET: async () => Response.json({ public: true }),
POST: {
middleware: [authMiddleware],
handler: async ({ context }) => {
return Response.json({ user: context.session.user })
},
},
}),
},
})tsx
export const Route = createFileRoute('/api/users')({
server: {
handlers: ({ createHandlers }) =>
createHandlers({
GET: async () => Response.json({ public: true }),
POST: {
middleware: [authMiddleware],
handler: async ({ context }) => {
return Response.json({ user: context.session.user })
},
},
}),
},
})Middleware Factories
中间件工厂
Create parameterized middleware for reusable patterns like authorization:
tsx
const authMiddleware = createMiddleware().server(async ({ next, request }) => {
const session = await auth.getSession({ headers: request.headers })
if (!session) throw new Error('Unauthorized')
return next({ context: { session } })
})Attachto everyauthMiddlewarethat needs auth. Server functions are API endpoints; a routecreateServerFndoes not protect their data, only the route's UI. Protect the endpoint that reads or mutates private data. See router-core/auth-and-guards and start-core/auth-server-primitives.beforeLoad
tsx
type Permissions = Record<string, string[]>
function authorizationMiddleware(permissions: Permissions) {
return createMiddleware({ type: 'function' })
.middleware([authMiddleware])
.server(async ({ next, context }) => {
const granted = await auth.hasPermission(context.session, permissions)
if (!granted) throw new Error('Forbidden')
return next()
})
}
// Usage
const getClients = createServerFn()
.middleware([authorizationMiddleware({ client: ['read'] })])
.handler(async () => {
return { message: 'The user can read clients.' }
})为授权等可复用模式创建参数化中间件:
tsx
const authMiddleware = createMiddleware().server(async ({ next, request }) => {
const session = await auth.getSession({ headers: request.headers })
if (!session) throw new Error('Unauthorized')
return next({ context: { session } })
})请将挂载到每个需要鉴权的authMiddleware上。 服务端函数就是 API 端点;路由的createServerFn无法保护它们的数据,只能保护路由的 UI。请保护那些读取或修改私有数据的端点。参见 router-core/auth-and-guards 和 start-core/auth-server-primitives。beforeLoad
tsx
type Permissions = Record<string, string[]>
function authorizationMiddleware(permissions: Permissions) {
return createMiddleware({ type: 'function' })
.middleware([authMiddleware])
.server(async ({ next, context }) => {
const granted = await auth.hasPermission(context.session, permissions)
if (!granted) throw new Error('Forbidden')
return next()
})
}
// Usage
const getClients = createServerFn()
.middleware([authorizationMiddleware({ client: ['read'] })])
.handler(async () => {
return { message: 'The user can read clients.' }
})Custom Headers and Fetch
自定义请求头与 Fetch
Setting headers from client middleware
从客户端中间件设置请求头
tsx
const authMiddleware = createMiddleware({ type: 'function' }).client(
async ({ next }) => {
return next({
headers: { Authorization: `Bearer ${getToken()}` },
})
},
)Headers merge across middleware. Later middleware overrides earlier. Call-site headers override all middleware headers.
tsx
const authMiddleware = createMiddleware({ type: 'function' }).client(
async ({ next }) => {
return next({
headers: { Authorization: `Bearer ${getToken()}` },
})
},
)请求头会在所有中间件间合并。靠后的中间件会覆盖靠前的中间件设置的请求头。调用处的请求头会覆盖所有中间件设置的请求头。
Custom fetch
自定义 fetch
tsx
// Use @tanstack/<framework>-start for your framework (react, solid, vue)
import type { CustomFetch } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware({ type: 'function' }).client(
async ({ next }) => {
const customFetch: CustomFetch = async (url, init) => {
console.log('Request:', url)
return fetch(url, init)
}
return next({ fetch: customFetch })
},
)Fetch precedence (highest to lowest): call site → later middleware → earlier middleware → createStart global → default fetch.
tsx
// Use @tanstack/<framework>-start for your framework (react, solid, vue)
import type { CustomFetch } from '@tanstack/react-start'
const loggingMiddleware = createMiddleware({ type: 'function' }).client(
async ({ next }) => {
const customFetch: CustomFetch = async (url, init) => {
console.log('Request:', url)
return fetch(url, init)
}
return next({ fetch: customFetch })
},
)Fetch 优先级(从高到低):调用处 → 靠后的中间件 → 靠前的中间件 → createStart 全局配置 → 默认 fetch。
Common Mistakes
常见错误
1. CRITICAL: Trusting client sendContext — shape check is not access check
1. 关键警告:轻信客户端 sendContext —— 结构检查不等于权限检查
sendContextLayer 1 — WRONG (no validation):
tsx
.server(async ({ next, context }) => {
// SQL-injectable AND tenant-walkable
await db.query(`SELECT * FROM workspace_${context.workspaceId}`)
return next()
})Layer 2 — STILL WRONG (shape only):
tsx
.server(async ({ next, context }) => {
// Looks safe, isn't. UUID is well-formed but the user may not be a member.
const workspaceId = z.string().uuid().parse(context.workspaceId)
await db.query('SELECT * FROM workspaces WHERE id = $1', [workspaceId])
return next()
})Layer 3 — CORRECT (shape AND access):
tsx
.middleware([authMiddleware]) // session loaded from cookie, NOT from sendContext
.server(async ({ next, context }) => {
const workspaceId = z.string().uuid().parse(context.workspaceId)
// Verify the session principal can access this workspace.
const member = await db.memberships.find({
userId: context.session.userId,
workspaceId,
})
if (!member) throw new Error('Not a member of this workspace')
await db.query('SELECT * FROM workspaces WHERE id = $1', [workspaceId])
return next({ context: { workspaceId } })
})The session itself must come from a server-trusted source (the cookie + DB lookup in ), never from — anything the client can send, the client can lie about. See start-core/auth-server-primitives.
authMiddlewaresendContext来自客户端中间件的 会作为不可信的客户端输入到达服务端。大多数开发者在用 Zod 解析完结构后就停止验证,认为值是安全的。其实不然:解析后的 UUID 只是某个工作区的 ID,不一定是发起请求的用户的工作区。如果没有针对会话主体的成员资格检查,你就构建了一个跨租户访问的端点。
sendContext第一层 —— 错误(无验证):
tsx
.server(async ({ next, context }) => {
// SQL-injectable AND tenant-walkable
await db.query(`SELECT * FROM workspace_${context.workspaceId}`)
return next()
})第二层 —— 仍然错误(仅结构验证):
tsx
.server(async ({ next, context }) => {
// Looks safe, isn't. UUID is well-formed but the user may not be a member.
const workspaceId = z.string().uuid().parse(context.workspaceId)
await db.query('SELECT * FROM workspaces WHERE id = $1', [workspaceId])
return next()
})第三层 —— 正确(结构 + 权限验证):
tsx
.middleware([authMiddleware]) // session loaded from cookie, NOT from sendContext
.server(async ({ next, context }) => {
const workspaceId = z.string().uuid().parse(context.workspaceId)
// Verify the session principal can access this workspace.
const member = await db.memberships.find({
userId: context.session.userId,
workspaceId,
})
if (!member) throw new Error('Not a member of this workspace')
await db.query('SELECT * FROM workspaces WHERE id = $1', [workspaceId])
return next({ context: { workspaceId } })
})会话本身必须来自服务端可信的来源( 中的 cookie + 数据库查询),绝不能来自 ——客户端能发送的任何内容都可能是伪造的。参见 start-core/auth-server-primitives。
authMiddlewaresendContext2. MEDIUM: Confusing request vs server function middleware
2. 中等严重:混淆请求中间件与服务端函数中间件
Request middleware runs on ALL requests (SSR, routes, functions). Server function middleware runs only for calls and has method.
createServerFn.client()请求中间件在所有请求上运行(SSR、路由、函数)。服务端函数中间件仅在 调用时运行,并且拥有 方法。
createServerFn.client()3. HIGH: Browser APIs in .client() crash during SSR
3. 高严重度:在 .client() 中使用浏览器 API 会导致 SSR 崩溃
During SSR, callbacks run on the server. Browser-only APIs like or will throw :
.client()localStoragewindowReferenceErrortsx
// WRONG — localStorage doesn't exist on the server during SSR
const middleware = createMiddleware({ type: 'function' }).client(
async ({ next }) => {
const token = localStorage.getItem('token')
return next({ sendContext: { token } })
},
)
// CORRECT — use cookies/headers or guard with typeof window check
const middleware = createMiddleware({ type: 'function' }).client(
async ({ next }) => {
const token =
typeof window !== 'undefined' ? localStorage.getItem('token') : null
return next({ sendContext: { token } })
},
)在 SSR 过程中, 回调会在服务端运行。仅浏览器可用的 API(如 或 )会抛出 :
.client()localStoragewindowReferenceErrortsx
// WRONG — localStorage doesn't exist on the server during SSR
const middleware = createMiddleware({ type: 'function' }).client(
async ({ next }) => {
const token = localStorage.getItem('token')
return next({ sendContext: { token } })
},
)
// CORRECT — use cookies/headers or guard with typeof window check
const middleware = createMiddleware({ type: 'function' }).client(
async ({ next }) => {
const token =
typeof window !== 'undefined' ? localStorage.getItem('token') : null
return next({ sendContext: { token } })
},
)4. MEDIUM: Wrong method order
4. 中等严重:方法顺序错误
tsx
// WRONG — type error
createMiddleware({ type: 'function' })
.server(() => { ... })
.client(() => { ... })
// CORRECT — middleware → validator → client → server
createMiddleware({ type: 'function' })
.middleware([dep])
.validator(schema)
.client(({ next }) => next())
.server(({ next }) => next())tsx
// WRONG — type error
createMiddleware({ type: 'function' })
.server(() => { ... })
.client(() => { ... })
// CORRECT — middleware → validator → client → server
createMiddleware({ type: 'function' })
.middleware([dep])
.validator(schema)
.client(({ next }) => next())
.server(({ next }) => next())Cross-References
相关参考
- start-core/server-functions — what middleware wraps
- start-core/server-routes — middleware on API endpoints
- start-core/auth-server-primitives — building the factory itself: session cookie reads, OAuth state, CSRF
authMiddleware - router-core/auth-and-guards — routing-side UX guards; data auth belongs in the server function, server route, or API endpoint handler/middleware
- start-core/server-functions —— 中间件所封装的内容
- start-core/server-routes —— API 端点上的中间件
- start-core/auth-server-primitives —— 构建 工厂本身:会话 cookie 读取、OAuth 状态、CSRF
authMiddleware - router-core/auth-and-guards —— 路由侧的 UX 守卫;数据鉴权应放在服务端函数、服务端路由或 API 端点的处理函数/中间件中