You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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目录的服务端中间件,能在请求到达页面之前拦截校验,适合全局登录态管控。

  1. 在项目根目录创建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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 06:47:07