Next.js从Pages Router演进到App Router后,请求处理模型发生了明显变化。中间件(middleware)在边缘运行时拦截请求,而页面组件以React Server Component形式参与渲染,二者不在同一个模块作用域。想把中间件中拿到的用户身份、地域信息或鉴权结果交给页面使用,需要走明确的信道,而不是靠内存变量。

为什么不能直接用全局变量传递
很多刚接触App Router的开发者会尝试在中间件里给global对象挂载属性,再在页面组件中读取。这种做法在本地单请求调试时看似正常,但部署到Serverless或多并发节点后,不同用户的请求会复用同一进程内存,导致数据错乱。中间件和页面分别处于请求链路的不同阶段,中间并无共享的实例上下文。
从底层看,中间件由Next.js的edge runtime执行,页面组件由Node.js runtime(或边缘兼容运行时)中的React渲染器处理。两者通过HTTP请求与响应对象通信,因此最稳妥的方式仍然是利用标准的HTTP机制,例如请求头、响应头与cookie。
方案一:通过请求头与headers接口传递
中间件可以在转发请求前,使用request.headers的set方法写入自定义头,页面组件内通过从next/headers导入的headers函数异步读取。该方式不暴露给浏览器,也不会出现在最终响应给客户端的头里,适合传递内部上下文。
下方示例展示中间件写入头信息,以及页面组件读取的过程:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(req: NextRequest) {
const res = NextResponse.next();
// 在请求头中注入用户区信息,供后续RSC读取
req.headers.set('x-region', 'cn-east');
req.headers.set('x-user-tier', 'basic');
return res;
}
export const config = {
matcher: '/dashboard/:path*'
};
// app/dashboard/page.tsx
import { headers } from 'next/headers';
export default async function DashboardPage() {
const h = await headers();
const region = h.get('x-region');
const tier = h.get('x-user-tier');
return (
<div>
<p>当前区域:{region}</p>
<p>用户等级:{tier}</p>
</div>
);
}
这种方法的优势是数据对客户端不可见,且不改变URL。缺点是请求头大小受平台限制,不适合放过大对象。另外,若使用了CDN缓存,需确认自定义头不会被缓存层丢弃。
方案二:借助cookie进行状态透传
当数据需要在客户端组件中也访问时,cookie比请求头更合适。中间件通过response.cookies.set写入,页面组件使用cookies函数读取,客户端组件则通过document.cookie或next/headers在SSR阶段拿到。
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(req: NextRequest) {
const res = NextResponse.next();
res.cookies.set('visitor_id', 'v_10086', {
httpOnly: true,
path: '/',
sameSite: 'lax'
});
return res;
}
// app/profile/page.tsx
import { cookies } from 'next/headers';
export default async function ProfilePage() {
const ck = await cookies();
const vid = ck.get('visitor_id')?.value;
return <p>访客编号:{vid}</p>;
}
cookie方案适合轻量标识,但需要注意隐私合规与体积。httpOnly的cookie无法被客户端JS读取,若要在浏览器侧用,需去掉该属性或另设非httpOnly副本。
方案三:页面内主动获取中间件上下文
如果中间件已调用外部接口拿到完整用户资料,可将其存入缓存(如内存缓存配合请求ID),页面组件再通过同一请求ID拉取。更简单的是把数据序列化进URL查询,但会暴露参数。推荐模式是中间件写头,页面用fetch向内部API路由取详情。
// app/api/ctx/route.ts
import { headers } from 'next/headers';
export async function GET() {
const h = await headers();
const region = h.get('x-region');
return Response.json({ region, from: 'internal' });
}
该模式将中间件视作预处理层,页面通过标准fetch走内部网络获取。它解耦了数据加工与渲染,便于单独测试,但在高并发下会增加一次内部调用开销,需要评估延迟容忍度。
选型建议与常见坑
仅服务端需要的上下文优先用请求头;需跨服务端客户端用cookie;复杂对象建议走内部API。避免用global,避免把大JSON塞进头。同时注意matcher配置,确保中间件覆盖目标路由,否则页面读到的头为空。
在App Router中,中间件与页面的数据传递本质是基于HTTP协议的协作。理清运行边界后,选用上述任一信道都能稳定落地,不必追求某一种银弹方案。
Next.jsApp_Routermiddleware修改时间:2026-07-31 23:00:46