服务器中间件
编辑页面
了解如何创建在 Expo Router 中对服务器的每个请求都运行的中间件。
For the complete documentation index, see llms.txt. Use this Use this file to discover all available pages.
Expo Router 中的 Server middleware 允许你在请求到达路由之前运行代码,从而为每个请求启用强大的服务端功能,例如身份验证和日志记录。与处理特定端点的 API routes 不同,middleware 会对应用中的每一个请求运行,因此它应尽可能快速,以避免拖慢应用性能。在原生端进行的客户端导航,或在 Web 应用中使用 <Link /> 时,都不会经过 server middleware。
设置
1
在应用配置中启用 server middleware
首先,通过在你的 app config 中添加服务器配置,将应用配置为使用 server 输出:
{ "expo": { %%placeholder-start%%... %%placeholder-end%% "web": { "output": "server" }, "plugins": [ [ "expo-router", { "unstable_useServerMiddleware": true } ] ] } }
2
创建你的 middleware 文件
在你的 src/app 目录中创建一个 +middleware.ts 文件,用于定义你的 server middleware 函数:
export default function middleware(request) { console.log(`Middleware executed for: ${request.url}`); // 你的 middleware 逻辑写在这里 }
middleware 函数必须作为该文件的默认导出。它接收一个不可变请求,并且可以返回一个 Response,或者什么都不返回以让请求按原样继续传递。请求是不可变的,以防止副作用;你可以读取 headers 和属性,但不能修改 headers 或消耗请求体。
5
配置 middleware 匹配器(可选)
默认情况下,middleware 会运行于所有服务器请求。你可以使用 unstable_settings 添加一个 matcher 来控制 middleware 何时执行:
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}`); }
matcher 配置允许你:
- 按 HTTP 方法过滤:指定哪些方法会触发 middleware
- 按路径模式过滤:使用精确路径、命名参数或正则表达式定义应匹配哪些 URL 模式
工作原理
middleware 函数在任何路由处理程序之前执行,使你能够执行日志记录、身份验证或修改响应等操作。它仅在服务器上运行,并且只针对实际的 HTTP 请求运行。
请求/响应流程
当请求到达你的应用时,Expo Router 会按以下顺序处理它:
- middleware 函数首先使用一个不可变请求运行。
- 如果 middleware 返回一个
Response,则会立即发送该响应 - 如果 middleware 什么都不返回,请求将继续进入匹配的路由
- 路由处理程序处理请求并返回其响应
模式匹配
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 不会在以下情况运行:
示例
身份验证
middleware 常用于在路由加载之前执行授权检查。你可以检查 headers、cookies 或查询参数,以确定用户是否有权访问某些路由:
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 记录请求,用于调试或分析。这有助于你跟踪用户活动或诊断应用中的问题:
export default function middleware(request) { console.log(`${request.method} ${request.url}`); }
动态重定向
middleware 也可以用于执行动态重定向。这使你能够根据特定条件控制用户导航:
export default function middleware(request) { if (request.headers.has('specific-header')) { return Response.redirect('https://expo.dev'); } }
仅 API 的 middleware
使用 matcher 仅对 API 路由运行 middleware,同时保持其他路由不受影响:
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),同时允许公开读取访问:
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 '); }
选择性日志记录
监控特定端点,而不是记录每个请求:
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}`); }
其他说明
最佳实践
- 保持 middleware 轻量,因为它会在每个服务器请求上同步运行,并直接影响响应时间。
- 使用 matcher 通过避免在不需要 middleware 的路由上执行它来优化性能,尤其适用于高流量应用。
- 对于简单模式,优先使用精确路径和命名参数,而不是 regex,因为它们比复杂正则表达式更快、更易维护。
- 结合方法和模式过滤,以精确控制 middleware 何时执行。
- 对于原生应用,使用 API routes 进行安全的数据获取。当原生应用调用 API routes 时,这些请求会先经过 middleware。
类型化 middleware
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;
限制
- middleware 仅在服务器上运行,并且只针对 HTTP 请求运行。它不会在客户端导航期间执行,例如使用
<Link />或原生应用的屏幕切换时。 - 传递给 middleware 的 request 对象是不可变的,以防止副作用。你不能修改 headers 或消耗请求体,从而确保它仍可供路由处理程序使用。
- 你的应用中只能有一个根级 +middleware.ts。
- 适用于 API routes 的相同限制也适用于 middleware。
请求不可变性
为防止意外副作用并确保请求体仍可供路由处理程序使用,传递给 middleware 的 Request 是不可变的。这意味着你可以:
- 读取所有请求属性,如
url、method、headers等 - 使用
request.headers.get()读取 header 值 - 使用
request.headers.has()检查 header 是否存在 - 访问 URL 参数和查询字符串
但你不能:
- 使用
set()、append()、delete()修改 headers - 使用
text()、json()、formData()等方法消耗请求体 - 直接访问
body属性