Next.js 13 App目录下Cookie校验报错原因及解决方案咨询
报错原因
你用的next/headers里的cookies()是Next.js专为服务端组件/服务端路由/中间件提供的API,只能在Node.js环境运行。而你的ExploreCourses加了"use client",属于客户端组件,会在浏览器里执行,根本访问不到服务端的Cookie读取API,所以直接触发报错。另外App目录彻底抛弃了getServerSideProps这类Pages时代的API,nookies依赖它的方案自然走不通。
正确实现方式
方案一:用Middleware做全局路由校验(推荐)
Middleware是App目录的服务端中间件,能在请求到达页面之前拦截校验,适合全局登录态管控。
- 在项目根目录创建
middleware.ts(不要放在app目录内):
import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' export function middleware(request: NextRequest) { // 服务端安全获取token Cookie const token = request.cookies.get('token')?.value // 定义需要校验的路由,比如/courses开头的页面 const protectedRoutes = ['/courses'] const isProtectedRoute = protectedRoutes.some(route => request.nextUrl.pathname.startsWith(route)) if (isProtectedRoute) { if (!token) { // 无token则重定向到登录页 return NextResponse.redirect(new URL('/login', request.url)) } // 服务端调用数据库校验token有效性(敏感逻辑完全在服务端,不暴露给前端) const isValidToken = await validateTokenFromDB(token) if (!isValidToken) { // token无效,清除Cookie并重定向 const response = NextResponse.redirect(new URL('/login?error=invalid_token', request.url)) response.cookies.delete('token') return response } } // 校验通过,放行请求 return NextResponse.next() } // 配置触发中间件的路由规则 export const config = { matcher: ['/courses/:path*'], } // 替换成你的实际数据库校验逻辑 async function validateTokenFromDB(token: string) { const res = await fetch(`${process.env.DB_API_URL}/validate-token`, { method: 'POST', body: JSON.stringify({ token }), headers: { 'Content-Type': 'application/json' }, }) return res.ok }
这个方案的优势:校验逻辑完全在服务端,安全性高;全局拦截无需每个页面重复写校验代码。
方案二:服务端组件做页面前置校验
如果只需要给单个页面做校验,可以在App目录的页面组件中用服务端组件做前置校验,客户端组件作为子组件渲染。
比如app/courses/page.tsx(默认是服务端组件,无需标记):
import { cookies } from 'next/headers' import { redirect } from 'next/navigation' import ExploreCourses from './ExploreCourses' export default async function CoursesPage() { const token = cookies().get('token')?.value if (!token) { redirect('/login') } // 服务端调用数据库校验token const isValid = await validateToken(token) if (!isValid) { redirect('/login?error=invalid_token') } // 校验通过,渲染客户端组件 return <ExploreCourses /> } // 替换成你的实际数据库校验逻辑 async function validateToken(token: string) { const res = await fetch(`${process.env.DB_URL}/check-token`, { method: 'POST', body: JSON.stringify({ token }), headers: { 'Content-Type': 'application/json' }, }) const data = await res.json() return data.valid }
你的ExploreCourses.tsx(客户端组件)只需专注页面渲染:
'use client' export default function ExploreCourses() { return <div>课程列表展示...</div> }
方案三:客户端组件辅助校验(仅做体验优化,不做安全拦截)
如果需要在客户端做状态提示(比如token过期弹窗),可以用浏览器原生document.cookie获取Cookie,但注意:客户端校验不能作为安全拦截依据,前端可篡改Cookie,必须以服务端校验结果为准。
ExploreCourses.tsx:
'use client' import { useEffect, useState } from 'react' import { useRouter } from 'next/navigation' export default function ExploreCourses() { const [tokenError, setTokenError] = useState('') const router = useRouter() useEffect(() => { // 客户端获取Cookie的工具函数 const getCookie = (name: string) => { const value = `; ${document.cookie}` const parts = value.split(`; ${name}=`) if (parts.length === 2) return parts.pop()?.split(';').shift() } const token = getCookie('token') if (!token) { router.push('/login') return } // 调用服务端API校验token(结果仅做提示,不能完全信任) fetch('/api/validate-token', { method: 'POST', body: JSON.stringify({ token }), headers: { 'Content-Type': 'application/json' }, }) .then(res => res.json()) .then(data => { if (!data.valid) { setTokenError('无效的登录凭证,请重新登录') // 清除客户端Cookie document.cookie = 'token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;' } }) }, [router]) if (tokenError) { return <div className="text-red-500">{tokenError}</div> } return <div>课程列表展示...</div> }
同时需要在app/api/validate-token/route.ts写服务端API处理校验:
import { NextResponse } from 'next/server' export async function POST(request: Request) { const { token } = await request.json() // 服务端调用数据库校验token const isValid = await validateTokenFromDB(token) return NextResponse.json({ valid: isValid }) } // 替换成你的实际数据库校验逻辑 async function validateTokenFromDB(token: string) { // ...你的DB查询逻辑 }
关键注意事项
- 安全优先:所有涉及敏感信息的校验(比如和数据库比对token)必须在服务端完成,客户端校验仅用于优化用户体验,不能作为安全屏障。
- App目录下,服务端组件默认无需标记,客户端组件必须加
"use client";服务端API(app/api/xx/route.ts)、Middleware均为服务端运行环境,可安全使用next/headers或request.cookies。
内容的提问来源于stack exchange,提问作者user15779851
相关产品推荐
相关产品推荐

