Next.js 用 cookies()/headers() 读请求信息:动态渲染触发、缓存失效与在 Server Action 里读写 cookie在 Next.js App Router 里想读一下用户的登录 token,或者拿一下User-Agent,你会发现 Server Component 里根本没有req对象可用。官方给的入口是next/headers里的cookies()和headers()。但很多人第一次用就踩坑:要么报「Dynamic server usage」构建失败,要么在 Server Component 里cookies().set()直接抛错。这篇把这两个函数的正确用法和背后的渲染机制讲透。基本用法:在 Server Component 里读 cookie 和 headercookies()和headers()只能在服务端运行的代码里调用——Server Components、Server Actions、Route Handlers。注意 Next.js 15 起它们是异步的,必须await。// app/dashboard/page.tsx —— 这是一个 Server Component import { cookies, headers } from next/headers export default async function Dashboard() { const cookieStore await cookies() const token cookieStore.get(token)?.value // 读单个 cookie const headerList await headers() const ua headerList.get(user-agent) ?? unknown // 读请求头 return ( div p登录态:{token ? 已登录 : 未登录}/p p你的浏览器:{ua}/p /div ) }如果你还在用 Next.js 14,cookies()/headers()是同步的,不用await。升级到 15 之后忘了加await会得到一个 Promise 而不是数据,.get is not a function就是这么来的。关键机制:调用它们会让整个路由变成动态渲染这是最容易忽略、又最影响性能的一点。Next.js 默认会尽量把页面静态化(构建时生成 HTML,请求时直接吐)。但 cookie 和 header 是每个请求都不一样的东西,一旦你在某个页面调用了cookies()或headers(),Next.js 就知道这个页面没法静态化了,会自动切成动态渲染(每次请求实时生成)。// 这个页面本来可以静态化,但因为读了 cookie,整页变成每次请求都渲染 export default async function Page() { const store await cookies() const theme store.get(theme)?.value return html>坑一:在 Server Component 里 set cookie 会直接报错cookies()返回的对象有.set()和.delete(),但你在 Server Component 里调它们会得到:Error: Cookies can only be modified in a Server Action or Route Handler原因是 Server Component 渲染时,HTTP 响应头可能已经开始发送了,没法再塞Set-Cookie。读 cookie 哪都行,写 cookie 只能在 Server Action 或 Route Handler 里。这是硬性边界。在 Server Action 里读写 cookie:一个登录例子Server Action 是写 cookie 的正确场所。下面是一个最小的登录流程:表单提交 → 校验 → 种 cookie → 刷新页面。// app/login/actions.ts use server import { cookies } from next/headers import { redirect } from next/navigation export async function login(formData: FormData) { const password formData.get(password) // 这里用假校验示意,真实项目请对接你的鉴权服务 if (password ! secret) { return { error: 密码错误 } } const cookieStore await cookies() cookieStore.set(token, signed-jwt-here, { httpOnly: true, // JS 读不到,防 XSS 窃取 secure: true, // 只在 HTTPS 下发送 sameSite: lax, // 防大部分 CSRF maxAge: 60 * 60 * 24 * 7, // 7 天 path: /, }) redirect(/dashboard) // 种完 cookie 跳转 }// app/login/page.tsx import { login } from ./actions export default function LoginPage() { return ( form action{login} input typepassword namepassword / button typesubmit登录/button /form ) }登出就是delete:use server import { cookies } from next/headers export async function logout() { const store await cookies() store.delete(token) // 删除 cookie }那几个 cookie 选项别偷懒:httpOnly挡住前端 JS 读取(防 XSS 把 token 偷走),secure强制 HTTPS,sameSite挡住跨站请求伪造。存登录 token 却不加httpOnly,等于把钥匙插门上。坑二:Route Handler 里改 cookie 要用响应对象在 Route Handler(route.ts)里,除了用cookies(),也可以直接操作NextResponse,后者在需要精确控制响应时更直观:// app/api/theme/route.tsimport{NextResponse}fromnext/serverexportasyncfunctionPOST(request:Request){const{theme}awaitrequest.json()constresNextResponse.json({ok:true})res.cookies.set(theme,theme,{path:/,maxAge:31536000})returnres// cookie 通过响应头下发}两种方式都行,选一种别混用。读 header 的一个实用场景:拿真实 IP反向代理(Nginx、Cloudflare)后面,客户端真实 IP 藏在转发头里,headers()正好用来读:import { headers } from next/headers async function getClientIP() { const h await headers() // 优先取代理链最左边的原始 IP,退回 x-real-ip const forwarded h.get(x-forwarded-for) return forwarded?.split(,)[0].trim() ?? h.get(x-real-ip) ?? unknown }注意x-forwarded-for是客户端可伪造的,别拿它做安全判断(比如「只有内网 IP 能访问」),它只适合做日志、地域展示这类非安全用途。小结cookies()/headers()来自next/headers,只能在服务端代码里用;Next.js 15 起是异步的,记得await。调用它们会让路由从静态渲染切成动态渲染——别在能静态化的页面里滥用,必要时用Suspense隔离个性化部分。读 cookie 到处可以,写 cookie 只能在 Server Action 或 Route Handler,在 Server Component 里 set 会直接报错。种登录 cookie 必带httpOnly secure sameSite,否则 token 容易被偷。x-forwarded-for可伪造,只用于日志/展示,别做安全判断。一句话记忆:读随处、写受限;一读就动态,想静态就隔离。