Next.js 14:中间件设置Cookie后根布局无法获取的问题
环境依赖
- react: 18.2.0
- next: 14.0.3
需求目标
确保请求初始阶段就存在带SessionId的Cookie,通过Middleware实现。
实现代码
Middleware代码
export async function middleware(req: NextRequest) { const response = NextResponse.next(); const ensuredSessionId = await fetchEnsureSession( req.nextUrl.origin, currentSessionId, ); console.log(`ensured sessionid for url ${req.url}`, ensuredSessionId); response.cookies.set("sessionid", ensuredSessionId, { path: "/", httpOnly: true, sameSite: "strict", secure: true, }); return response; }
根布局代码
import { CartProvider } from "@storefront/shell/cart/cartcontext"; import { cookies } from "next/headers"; interface Props { children: React.ReactNode; } export default async function RootLayout({ children }: Props) { const session = cookies().get("sessionid"); console.log("session-Id", session); return ( <CartProvider sessionId={session?.value}>{children}</CartProvider> ); }
问题现象
首次加载页面时,根布局中读取sessionid Cookie的结果为undefined,但浏览器开发者工具能看到该Cookie已存在;刷新页面后,根布局就能正常获取到Cookie值。
Next.js日志
✓ Ready in 1773ms ✓ Compiled /src/middleware in 178ms (163 modules) ✓ Compiled /api/ensure-session in 455ms (33 modules) ensured sessionId for url https://localhost:3000/en f142d570-93a7-4503-a52e-7f9fe580b89f ○ Compiling /[lang] ... ✓ Compiled /[lang] in 2.9s (3517 modules) Session-Id undefined ○ Compiling /api/trpc/[trpc] ... ✓ Compiled /api/trpc/[trpc] in 573ms (2083 modules)
疑问
原本以为Middleware执行顺序在根布局之前,首次加载时Cookie应该已经存在,但实际并非如此。对Next.js Cookie机制的理解存在哪些误区?
你核心的误区是混淆了Middleware设置Cookie的时机和服务器组件读取Cookie的来源:
Middleware的执行逻辑:
Middleware确实在根布局(服务器组件)执行前运行,但它是在处理当前请求的响应阶段设置Cookie——这个Cookie是要发送给浏览器的响应头内容,而非当前请求的请求头内容。服务器组件读取Cookie的来源:
根布局里用cookies().get("sessionid")读取的是当前请求从浏览器发送过来的Cookie(即请求头里的Cookie字段)。首次加载时,浏览器还没收到Middleware设置的Cookie,自然不会在请求头里带上,所以服务器组件读不到。刷新后能读取的原因:
首次加载的响应返回后,浏览器保存了这个Cookie;刷新页面时,浏览器会把该Cookie放在请求头里发送给服务器,此时服务器组件就能从请求头里读到这个Cookie了。
解决方法
方法1:Middleware传递SessionId到服务器组件
通过NextResponse.next()的headers属性,把SessionId作为自定义头传递,根布局直接读取这个自定义头:
修改Middleware:
export async function middleware(req: NextRequest) { const ensuredSessionId = await fetchEnsureSession( req.nextUrl.origin, currentSessionId, ); const response = NextResponse.next({ headers: { "x-session-id": ensuredSessionId, }, }); response.cookies.set("sessionid", ensuredSessionId, { path: "/", httpOnly: true, sameSite: "strict", secure: true, }); return response; }
修改根布局:
import { CartProvider } from "@storefront/shell/cart/cartcontext"; import { headers } from "next/headers"; interface Props { children: React.ReactNode; } export default async function RootLayout({ children }: Props) { const sessionId = headers().get("x-session-id"); console.log("session-Id", sessionId); return ( <CartProvider sessionId={sessionId}>{children}</CartProvider> ); }
方法2:服务器组件直接调用SessionId生成逻辑
既然Middleware里的fetchEnsureSession是用来确保SessionId存在的,直接在根布局里调用这个函数,跳过对Cookie的依赖:
import { CartProvider } from "@storefront/shell/cart/cartcontext"; import { cookies } from "next/headers"; import { fetchEnsureSession } from "path-to-your-function"; interface Props { children: React.ReactNode; } export default async function RootLayout({ children }: Props) { const currentSession = cookies().get("sessionid")?.value; const sessionId = await fetchEnsureSession( process.env.NEXT_PUBLIC_APP_URL, currentSession ); console.log("session-Id", sessionId); return ( <CartProvider sessionId={sessionId}>{children}</CartProvider> ); }
这样首次加载就能直接拿到SessionId,同时Middleware依然负责把Cookie设置到响应中,后续请求可复用该Cookie。
内容的提问来源于stack exchange,提问作者Dmitry Klymenko

