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 中对服务器的每个请求都运行的中间件。


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

设置

1

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

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

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

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

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

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 就会运行。当同时指定 methodspatterns 时,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 是不可变的。这意味着你可以:

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

但你不能:

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