This documentation is available as Markdown for AI agents and LLMs. See the full Markdown index or append .md to any documentation URL.

服务器中间件

编辑页面

了解如何创建在每个发送到 Expo Router 服务器的请求中运行的中间件。


在 SDK 58 及更高版本中,unstable_useServerMiddleware 已弃用且不起作用。请将其从配置中移除,以避免弃用警告。

Expo Router 中的服务器中间件允许你在请求到达路由之前运行代码,从而为每个请求启用强大的服务端功能,例如身份验证和日志记录。与处理特定端点的 API 路由 不同,middleware 会对应用中的每一个请求运行,因此它应尽可能快速,以避免拖慢应用性能。在原生端进行的客户端导航,或在 Web 应用中使用 <Link /> 时,都不会经过服务器中间件。

设置

1

在应用配置中启用服务器中间件

首先,通过在你的 应用配置 中添加服务器配置,将应用配置为使用服务器输出:

app.json
{ "expo": { %%placeholder-start%%... %%placeholder-end%% "web": { "output": "server" }, "plugins": ["expo-router"] } }

在 SDK 58 及更高版本中,当 expo-router 配置插件中设置了 apiRoutes: true 时,你可以在 web.output: "static" 模式下使用 middleware。这使 middleware 能够在预渲染 HTML 和 API 路由之前运行。

2

创建你的中间件文件

在你的 src/app 目录中创建一个 +middleware.ts 文件,用于定义你的服务器中间件函数:

src/app/+middleware.ts
export default function middleware(request) { console.log(`Middleware executed for: ${request.url}`); // 你的中间件逻辑写在这里 }

中间件函数必须作为该文件的默认导出。它接收一个不可变请求,并且可以返回一个 Response,或者什么都不返回以让请求按原样继续传递。请求是不可变的,以防止副作用;你可以读取标头和属性,但不能修改标头或消耗请求体。

3

启动你的开发服务器

运行你的开发服务器以测试中间件:

Terminal
- npx expo start
- yarn expo start
- pnpm expo start
- bun expo start

现在你的中间件将对应用的所有请求运行。

4

测试中间件功能

在浏览器中访问你的应用,或发起请求来测试中间件是否正常工作。检查控制台中来自中间件函数的日志消息。

5

配置中间件匹配器(可选)

默认情况下,中间件会运行于所有服务器请求。你可以使用 unstable_settings 添加一个匹配器来控制中间件何时执行:

src/app/+middleware.ts
export const unstable_settings = { matcher: { // 仅在 GET 请求上运行 methods: ['GET'], // 仅在 API 路由和特定路径上运行 patterns: ['/api', '/admin/[...path]'], }, }; export default function middleware(request) { console.log(`Middleware executed for: ${request.url}`); }

匹配器配置允许你:

  • 按 HTTP 方法过滤:指定哪些方法会触发中间件
  • 按路径模式过滤:使用精确路径、命名参数或正则表达式定义应匹配哪些 URL 模式

工作原理

middleware 函数在任何路由处理程序之前执行,使你能够执行日志记录、身份验证或修改响应等操作。它仅在服务器上运行,并且只针对实际的 HTTP 请求运行。

请求/响应流程

当请求到达你的应用时,Expo Router 会按以下顺序处理它:

  1. middleware 函数首先使用一个不可变请求运行。
  2. 如果 middleware 返回一个 Response,则会立即发送该响应
  3. 如果 middleware 什么都不返回,请求将继续进入匹配的路由
  4. 路由处理程序处理请求并返回其响应

模式匹配

matcher 支持不同类型的模式,以控制 middleware 何时运行:

export const unstable_settings = { matcher: { patterns: [ '/api', // 精确路径 '/posts/[postId]', // 命名参数 '/blog/[...slug]', // 捕获所有参数 /^\/api\/v\d+\/users$/, // 正则表达式 ], }, };
  • 精确路径 只匹配指定路径。/api 匹配 /api,但不匹配 /api/users
  • 命名参数 如 [postId] 会捕获任意单个段。/posts/[postId] 匹配 /posts/123 或 /posts/my-post
  • 捕获所有参数 如 [...slug] 会捕获一个或多个段。/blog/[...slug] 匹配 /blog/2024 或 /blog/2024/12/post
  • 正则表达式 用于复杂模式。/^\/api\/v\d+\/users$/ 匹配 /api/v1/users,但不匹配 /api/users

如果任意一个模式匹配请求 URL,middleware 就会运行。当同时指定 methods 和 patterns 时,middleware 运行必须同时满足这两个条件。

Middleware 执行顺序

Expo Router 支持一个名为 +middleware.ts 的单一 middleware 文件,它会针对所有服务器请求运行。使用 matcher 时,middleware 只会对匹配指定模式和方法的请求执行,并且发生在任何路由匹配或渲染之前。

middleware 何时运行

middleware 仅针对发送到你服务器的实际 HTTP 请求执行。这意味着它会在以下情况执行:

  • 首次页面加载,例如用户第一次访问你的网站
  • 整页刷新
  • 直接 URL 导航
  • 来自任何客户端(原生/Web 应用、外部服务)的 API 路由调用
  • 服务端渲染请求

middleware 不会在以下情况运行:

  • 使用 <Link /> 或 router 进行客户端导航
  • 原生应用屏幕切换
  • 预取的路由
  • 图像和字体等静态资源请求

示例

身份验证

middleware 常用于在路由加载之前执行授权检查。你可以检查 headers、cookies 或查询参数,以确定用户是否有权访问某些路由:

src/app/+middleware.ts
import { jwtVerify } from 'jose'; export default function middleware(request) { const token = request.headers.get('authorization'); const decoded = jwtVerify(token, process.env.SECRET_KEY); if (!decoded.payload) { return new Response('Forbidden', { status: 403 }); } }
日志记录

你可以使用 middleware 记录请求,用于调试或分析。这有助于你跟踪用户活动或诊断应用中的问题:

src/app/+middleware.ts
export default function middleware(request) { console.log(`${request.method} ${request.url}`); }
动态重定向

middleware 也可以用于执行动态重定向。这使你能够根据特定条件控制用户导航:

src/app/+middleware.ts
export default function middleware(request) { if (request.headers.has('specific-header')) { return Response.redirect('https://expo.dev'); } }
仅 API 的 middleware

使用 matcher 仅对 API 路由运行 middleware,同时保持其他路由不受影响:

src/app/+middleware.ts
export const unstable_settings = { matcher: { patterns: ['/api'], }, }; export default function middleware(request) { // 记录所有 API 请求以用于调试 console.log(`API request: ${request.method} ${request.url}`); // 为 API 路由添加 CORS 头 const response = new Response(); response.headers.set('Access-Control-Allow-Origin', '*'); return response; }
按方法进行身份验证

保护写操作(POST、PUT、DELETE),同时允许公开读取访问:

src/app/+middleware.ts
export const unstable_settings = { matcher: { methods: ['POST', 'PUT', 'DELETE'], patterns: ['/api', '/admin/[...path]'], }, }; export default function middleware(request) { const token = request.headers.get('authorization'); if (!token || !isValidToken(token)) { return new Response('Unauthorized', { status: 401 }); } } function isValidToken(token: string): boolean { // 你的 token 验证逻辑 return token.startsWith('Bearer '); }
选择性日志记录

监控特定端点,而不是记录每个请求:

src/app/+middleware.ts
export const unstable_settings = { matcher: { patterns: ['/api/users/[userId]', '/admin', /^\/webhook/], }, }; export default function middleware(request) { const userAgent = request.headers.get('user-agent'); const timestamp = new Date().toISOString(); console.log(`[${timestamp}] ${request.method} ${request.url} - ${userAgent}`); }

其他说明

最佳实践

  • 保持中间件轻量,因为它会在每个服务器请求上同步运行,并直接影响响应时间。
  • 使用 matcher,通过避免在不需要中间件的路由上执行中间件来优化性能,尤其适用于高流量应用。
  • 对于简单模式,优先使用精确路径和命名参数,而不是正则表达式,因为它们比复杂正则表达式更快、更易维护。
  • 结合方法和模式过滤,以精确控制中间件何时执行。
  • 对于原生应用,使用 API 路由进行安全的数据获取。当原生应用调用 API 路由时,这些请求会先经过中间件。

类型化中间件

src/app/+middleware.ts
import { MiddlewareFunction } from 'expo-router/server'; const middleware: MiddlewareFunction = request => { if (request.headers.has('specific-header')) { return Response.redirect('https://expo.dev'); } }; export default middleware;

限制

  • 中间件仅在服务器上运行,并且只针对 HTTP 请求运行。它不会在客户端导航期间执行,例如使用 <Link /> 或原生应用的屏幕切换时。
  • 传递给中间件的请求对象是不可变的,以防止副作用。你不能修改请求头或消耗请求体,从而确保它仍可供路由处理程序使用。
  • 你的应用中只能有一个根级 +middleware.ts。
  • 适用于 API 路由的相同限制也适用于中间件。

请求不可变性

为防止意外副作用并确保请求体仍可供路由处理程序使用,传递给中间件的 Request 是不可变的。这意味着你可以:

  • 读取所有请求属性,如 url、method、headers 等
  • 使用 request.headers.get() 读取请求头值
  • 使用 request.headers.has() 检查请求头是否存在
  • 访问 URL 参数和查询字符串

但你不能:

  • 使用 set()、append()、delete() 修改请求头
  • 使用 text()、json()、formData() 等方法消耗请求体
  • 直接访问 body 属性。