如何在NextJS 12.0的其他函数中访问Express请求?
在Next.js 12里并没有像PHP框架那样直接的全局request()函数,但可以通过以下几种方式实现类似效果,满足日志埋点、请求ID追踪的需求:
1. 用Async Local Storage实现全局请求上下文(Node.js环境)
Next.js 12的Node.js运行时支持Node的async_hooks模块,我们可以用它创建请求上下文容器,在请求进入时保存信息,后续任何异步代码都能取出:
步骤1:创建上下文管理模块
// lib/requestContext.js const { AsyncLocalStorage } = require('async_hooks'); const asyncLocalStorage = new AsyncLocalStorage(); // 保存请求上下文 function setRequestContext(req) { const context = { requestUrl: req.url, referrer: req.headers.referer || '', userAgent: req.headers['user-agent'] || '', requestId: req.headers['x-request-id'] || generateRequestId() // 自定义生成请求ID }; return asyncLocalStorage.run(context, () => {}); } // 获取当前上下文(无请求时返回null) function getRequestContext() { return asyncLocalStorage.getStore() || null; } // 简单的请求ID生成函数 function generateRequestId() { return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); } module.exports = { setRequestContext, getRequestContext };
步骤2:在自定义服务器或入口中间件中注入上下文
如果用自定义Express服务器:
// server.js const express = require('express'); const next = require('next'); const { setRequestContext } = require('./lib/requestContext'); const app = express(); const nextApp = next({ dev: process.env.NODE_ENV !== 'production' }); const handle = nextApp.getRequestHandler(); nextApp.prepare().then(() => { app.use((req, res, next) => { // 注入请求上下文 setRequestContext(req); next(); }); app.all('*', (req, res) => { return handle(req, res); }); app.listen(3000, () => { console.log('Server running on http://localhost:3000'); }); });
如果不用自定义服务器,也可以在pages/_app.js的getInitialProps中注入(注意:getInitialProps会让所有页面变为SSR,按需使用):
// pages/_app.js import { setRequestContext } from '../lib/requestContext'; function MyApp({ Component, pageProps }) { return <Component {...pageProps} />; } MyApp.getInitialProps = async ({ ctx }) => { if (ctx.req) { setRequestContext(ctx.req); } return { pageProps: {} }; }; export default MyApp;
步骤3:在日志类中使用
// lib/logger.js const { getRequestContext } = require('./requestContext'); function log(message) { const context = getRequestContext(); const logData = { message, timestamp: new Date().toISOString(), ...(context ? { requestUrl: context.requestUrl, referrer: context.referrer, userAgent: context.userAgent, requestId: context.requestId } : {}) }; console.log(JSON.stringify(logData)); } module.exports = { log };
不管是在getServerSideProps、API路由还是自定义中间件里,调用log()都会自动带上请求上下文;无请求场景(比如getStaticProps)下,只会输出基础日志内容。
2. 在getServerSideProps中直接传递请求信息
如果不需要全局上下文,仅在页面SSR逻辑中使用,可以直接在getServerSideProps里提取请求参数,传递给组件或日志函数:
// pages/some-page.js import { log } from '../lib/logger'; export async function getServerSideProps(ctx) { const { req } = ctx; const requestContext = { requestUrl: req.url, referrer: req.headers.referer, userAgent: req.headers['user-agent'] }; // 直接传入日志函数 log('Page loaded', requestContext); return { props: { requestContext } // 也可以传递给组件使用 }; }
3. Edge Middleware中的处理(Next.js 12 Edge Runtime)
如果使用Next.js 12的Edge Middleware(middleware.js),由于Edge Runtime不支持Node的async_hooks,可以通过NextResponse传递请求ID,或直接在Middleware中处理日志:
// middleware.js import { NextResponse } from 'next/server'; export function middleware(req) { const requestId = req.headers.get('x-request-id') || generateRequestId(); const response = NextResponse.next(); // 把请求ID设置到响应头,后续API路由或页面可读取 response.headers.set('x-request-id', requestId); // 直接记录日志 console.log({ message: 'Request received', requestUrl: req.url, referrer: req.headers.get('referer'), userAgent: req.headers.get('user-agent'), requestId }); return response; } function generateRequestId() { return crypto.randomUUID(); // Edge Runtime原生支持crypto模块 }
在Edge环境的API路由或getServerSideProps里,可通过req.headers.get('x-request-id')获取请求ID,其他请求信息也能直接从req对象提取。
注意:getStaticProps在构建阶段运行,确实没有请求上下文,此时getRequestContext()会返回null,日志不会包含请求相关字段,符合需求。
内容的提问来源于stack exchange,提问作者Kristi Jorgji

