middleware

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Middleware

中间件

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()
→
server()
. Wrong order causes type errors. CRITICAL: Validating the shape of
sendContext
(e.g.
z.string().uuid().parse(...)
) 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.
中间件用于自定义服务端函数和服务端路由的行为。它是可组合的——中间件可以依赖其他中间件形成调用链。
关键警告:TypeScript 会强制方法调用顺序:
middleware()
→
validator()
→
client()
→
server()
。顺序错误会导致类型错误。 关键警告:验证
sendContext
的结构(例如
z.string().uuid().parse(...)
)并不等同于授权。解析后的标识符只是格式正确的标识符,并不代表已授权。在将客户端发送的 ID 用作查询键、过滤器或路径参数之前,始终要根据会话主体重新检查访问权限。

Two Types of Middleware

两种中间件类型

FeatureRequest MiddlewareServer Function Middleware
ScopeAll server requests (SSR, routes, functions)Server functions only
Methods
.server()
.client()
,
.server()
Input validationNoYes (
.validator()
)
Client-side logicNoYes
Created with
createMiddleware()
createMiddleware({ type: 'function' })
Request middleware cannot depend on server function middleware. Server function middleware can depend on both types.
特性请求中间件服务端函数中间件
适用范围所有服务端请求(SSR、路由、函数)仅服务端函数
可用方法
.server()
.client()
、
.server()
输入验证不支持支持(
.validator()
)
客户端逻辑不支持支持
创建方式
createMiddleware()
createMiddleware({ type: 'function' })
请求中间件不能依赖服务端函数中间件。服务端函数中间件可以同时依赖两种类型的中间件。

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
src/start.ts
to configure global middleware:
tsx
// 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.ts
文件来配置全局中间件:
tsx
// 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 } })
})
Attach
authMiddleware
to every
createServerFn
that needs auth.
Server functions are API endpoints; a route
beforeLoad
does 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.
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
挂载到每个需要鉴权的
createServerFn
上。
服务端函数就是 API 端点;路由的
beforeLoad
无法保护它们的数据,只能保护路由的 UI。请保护那些读取或修改私有数据的端点。参见 router-core/auth-and-guards 和 start-core/auth-server-primitives。
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 —— 结构检查不等于权限检查

sendContext
from a client middleware arrives on the server as untrusted client input. Most agents stop after parsing the shape with Zod and assume the value is safe. It isn't: a parsed UUID is some workspace, not the requesting user's workspace. Without a membership check against the session principal, you've built a tenant-walking endpoint.
Layer 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
authMiddleware
), never from
sendContext
— anything the client can send, the client can lie about. See start-core/auth-server-primitives.
来自客户端中间件的
sendContext
会作为不可信的客户端输入到达服务端。大多数开发者在用 Zod 解析完结构后就停止验证,认为值是安全的。其实不然:解析后的 UUID 只是某个工作区的 ID,不一定是发起请求的用户的工作区。如果没有针对会话主体的成员资格检查,你就构建了一个跨租户访问的端点。
第一层 —— 错误(无验证):
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 } })
})
会话本身必须来自服务端可信的来源(
authMiddleware
中的 cookie + 数据库查询),绝不能来自
sendContext
——客户端能发送的任何内容都可能是伪造的。参见 start-core/auth-server-primitives。

2. MEDIUM: Confusing request vs server function middleware

2. 中等严重:混淆请求中间件与服务端函数中间件

Request middleware runs on ALL requests (SSR, routes, functions). Server function middleware runs only for
createServerFn
calls and has
.client()
method.
请求中间件在所有请求上运行(SSR、路由、函数)。服务端函数中间件仅在
createServerFn
调用时运行,并且拥有
.client()
方法。

3. HIGH: Browser APIs in .client() crash during SSR

3. 高严重度:在 .client() 中使用浏览器 API 会导致 SSR 崩溃

During SSR,
.client()
callbacks run on the server. Browser-only APIs like
localStorage
or
window
will throw
ReferenceError
:
tsx
// 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 过程中,
.client()
回调会在服务端运行。仅浏览器可用的 API(如
localStorage
或
window
)会抛出
ReferenceError
:
tsx
// 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
    authMiddleware
    factory itself: session cookie reads, OAuth state, CSRF
  • 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 —— 构建
    authMiddleware
    工厂本身:会话 cookie 读取、OAuth 状态、CSRF
  • router-core/auth-and-guards —— 路由侧的 UX 守卫;数据鉴权应放在服务端函数、服务端路由或 API 端点的处理函数/中间件中