服务器中间件
了解如何在 Expo Router 中创建为每个服务器请求运行的中间件。
重要 服务器中间件目前处于 alpha 阶段,可在 SDK 54 及更高版本中使用。生产环境使用需要一个已部署的服务器。
Expo Router 中的服务器中间件允许你在请求到达路由之前运行代码,从而实现强大的服务器端功能,例如对每个请求进行身份验证和日志记录。与处理特定端点的 API 路由 不同,中间件会对你应用中的每个请求运行,因此它应该尽可能快速运行,以避免降低应用的性能。在客户端导航时,例如在原生应用中,或者在使用 <Link /> 的 Web 应用中,服务器中间件不会被执行。
🌐 Server middleware in Expo Router allows you to run code before requests reach your routes, enabling powerful server-side functionality like authentication and logging for every request. Unlike API routes that handle specific endpoints, middleware runs for every request in your app, so it should run as quickly as possible to avoid slowing down your app's performance. Client-side navigation such as on native, or in a web app when using <Link />, will not move through the server middleware.
设置
🌐 Setup
1
在应用配置中启用服务器中间件
🌐 Enable server middleware in your app configuration
首先,通过将服务器配置添加到你的应用配置中,来配置你的应用以使用服务器输出:
🌐 First, configure your app to use server output by adding the server configuration to your app config:
{ "expo": { %%placeholder-start%%... %%placeholder-end%% "web": { "output": "server" }, "plugins": [ [ "expo-router", { "unstable_useServerMiddleware": true } ] ] } }
2
创建你的中间件文件
🌐 Create your middleware file
在你的 app 目录中创建一个 +middleware.ts 文件,用于定义你的服务器中间件函数:
🌐 Create a +middleware.ts file in your app directory, to define your server middleware function:
export default function middleware(request) { console.log(`Middleware executed for: ${request.url}`); // Your middleware logic goes here }
中间件函数必须是文件的默认导出。它接收一个不可变请求,可以返回一个Response,或者不返回任何内容以允许请求不被修改地通过。请求是不可变的,以防止副作用;你可以读取头信息和属性,但不能修改头信息或消耗请求体。
🌐 The middleware function must be the default export of the file. It receives an immutable request and can return either a Response, or nothing to let the request pass through unmodified. The request is immutable to prevent side effects; you can read headers and properties, but you cannot modify headers or consume the request body.
3
4
5
配置中间件匹配器(可选)
🌐 Configure middleware matchers (optional)
默认情况下,中间件会在所有服务器请求上运行。你可以添加一个匹配器来控制中间件何时执行,使用 unstable_settings:
🌐 By default, middleware runs on all server requests. You can add a matcher to control when your middleware executes with unstable_settings:
export const unstable_settings = { matcher: { // Only run on GET requests methods: ['GET'], // Only run on API routes and specific paths patterns: ['/api', '/admin/[...path]'], }, }; export default function middleware(request) { console.log(`Middleware executed for: ${request.url}`); }
匹配器配置允许你:
🌐 The matcher configuration allows you to:
- 按 HTTP 方法过滤:指定哪些方法应触发中间件
- 按路径模式筛选:使用精确路径、命名参数或正则表达式定义应匹配的 URL 模式
工作原理
🌐 How it works
中间件函数在任何路由处理器之前执行,允许你执行诸如记录日志、身份验证或修改响应等操作。它仅在服务器上运行,并且只针对实际的 HTTP 请求。
🌐 Middleware functions are executed before any route handlers, allowing you to perform actions like logging, authentication, or modifying responses. It runs exclusively on the server and only for actual HTTP requests.
请求/响应流程
🌐 Request/response flow
当请求到达你的应用时,Expo Router 会按以下顺序处理:
🌐 When a request comes to your app, Expo Router processes it in this order:
- 中间件函数首先运行,并带有一个不可变请求。
- 如果中间件返回
Response,该响应会立即发送 - 如果中间件没有返回内容,请求将继续匹配对应的路由
- 路由处理程序处理请求并返回响应
模式匹配
🌐 Pattern matching
匹配器支持不同的模式类型,以控制中间件何时运行:
🌐 Matchers support different pattern types to control when middleware runs:
export const unstable_settings = { matcher: { patterns: [ '/api', // Exact path '/posts/[postId]', // Named parameter '/blog/[...slug]', // Catch-all parameter /^\/api\/v\d+\/users$/, // Regular expression ], }, };
- 精确路径 仅匹配指定的路径。
/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,中间件就会运行。当同时指定 methods 和 patterns 时,中间件必须满足两个条件才能运行。
🌐 Middleware runs if any pattern matches the request URL. When both methods and patterns are specified, both conditions must be met for middleware to run.
中间件执行顺序
🌐 Middleware execution order
Expo Router 支持一个名为 +middleware.ts 的单一中间件文件,该文件会对所有服务器请求运行。使用匹配器时,中间件仅在请求符合指定的模式和方法时执行,并且在任何路由匹配或渲染发生之前执行。
🌐 Expo Router supports a single middleware file named +middleware.ts that runs for all server requests. When using matchers, middleware executes only for requests that match the specified patterns and methods, before any route matching or rendering occurs.
当中间件运行时
🌐 When middleware runs
中间件仅在对你的服务器的实际 HTTP 请求时执行。这意味着它会在以下情况下执行:
🌐 Middleware executes only for actual HTTP requests to your server. This means it is executed for:
- 初始页面加载,例如用户首次访问你的网站时
- 完整页面刷新
- 直接网址导航
- 来自任何客户端(原生/网页应用、外部服务)的 API 路由调用
- 服务器端渲染请求
中间件不会针对以下情况运行:
🌐 Middleware does not run for:
示例
🌐 Examples
验证
中间件通常用于在路由加载之前执行授权检查。你可以检查请求头、Cookies 或查询参数,以确定用户是否有权限访问某些路由:
🌐 Middleware is often used to perform authorization checks before a route has loaded. You can check headers, cookies, or query parameters to determine if a user has access to certain routes:
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 }); } }
伐木
你可以使用中间件来记录请求,用于调试或分析目的。这可以帮助你跟踪用户活动或诊断应用中的问题:
🌐 You can use middleware to log requests for debugging or analytics purposes. This can help you track user activity or diagnose issues in your app:
export default function middleware(request) { console.log(`${request.method} ${request.url}`); }
动态重定向
中间件也可以用于执行动态重定向。这使你能够根据特定条件控制用户导航:
🌐 Middleware can also be used to perform dynamic redirects. This allows you to control user navigation based on specific conditions:
export default function middleware(request) { if (request.headers.has('specific-header')) { return Response.redirect('https://expo.dev'); } }
仅限 API 的中间件
使用匹配器仅针对 API 路由运行中间件,保持其他路由不受影响:
🌐 Use matchers to run middleware only for API routes, keeping other routes unaffected:
export const unstable_settings = { matcher: { patterns: ['/api'], }, }; export default function middleware(request) { // Log all API requests for debugging console.log(`API request: ${request.method} ${request.url}`); // Add CORS headers for API routes const response = new Response(); response.headers.set('Access-Control-Allow-Origin', '*'); return response; }
特定方法认证
在允许公共读取访问的同时保护写操作(POST、PUT、DELETE):
🌐 Protect write operations (POST, PUT, DELETE) while allowing public read access:
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 { // Your token validation logic return token.startsWith('Bearer '); }
选择性采伐
监控特定端点而不记录每个请求:
🌐 Monitor specific endpoints without logging every request:
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}`); }
附加说明
🌐 Additional notes
最佳实践
🌐 Best practices
- 保持中间件轻量化,因为它在每个服务器请求上同步运行,并直接影响响应时间。
- 使用匹配器来优化性能,避免在不需要的路由上执行不必要的中间件,尤其适用于高流量应用。
- 比起正则表达式,更倾向于使用精确路径和命名参数,因为简单的模式比复杂的正则表达式更容易评估和维护。
- 结合方法和模式过滤,以精确控制中间件的执行时机。
- 对于原生应用,请使用 API 路由进行安全的数据获取。当原生应用调用 API 路由时,这些请求将首先通过中间件。
类型化中间件
🌐 Typed 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;
局限性
🌐 Limitations
- 中间件仅在服务器上运行,并且仅适用于 HTTP 请求。它不会在客户端导航期间执行,例如使用
<Link />或原生应用屏幕切换时。 - 传递给中间件的请求对象是不可变的,以防止副作用。你无法修改头信息或消费请求体,从而确保它在路由处理程序中仍然可用。
- 在你的应用中,你只能有一个根级别的 +middleware.ts。
- 同样适用于 API 路由 的限制也适用于中间件。
请求不可变性
🌐 Request immutability
为了防止意外的副作用并确保请求体在路由处理程序中仍然可用,传递给中间件的 Request 是不可变的。这意味着你可以:
🌐 To prevent unintended side effects and ensure the request body remains available for route handlers, the Request passed to middleware is immutable. This means you can:
- 读取所有请求属性,如
url、method、headers等 - 使用
request.headers.get()读取头部值 - 检查是否存在
request.headers.has()头 - 访问 URL 参数和查询字符串
但你无法执行以下操作:
🌐 But you won't be able to:
- 修改带有
set()、append()、delete()的标题 - 使用
text()、json()、formData()等来处理请求体 - 直接访问
body属性