NextJS 14.0.3服务端组件获取Cookies报错及API请求问题求助
问题分析与解决方案
错误根源
next/headers的cookies()是服务端专属API,仅能在Server Components、API路由、Server Actions中使用。如果在标记了"use client"的客户端组件中调用,会触发环境不匹配错误;反之,若给fetcher添加"use client",又会导致它无法在API路由(服务端环境)中被导入使用。
解决方案1:让fetcher兼容服务端/客户端双环境
通过环境判断+动态导入,避免服务端API在客户端加载,同时分别用对应方式获取token:
import { HttpStatusCode } from 'axios' import { BASE_API_URL } from './axios' interface FetchOptions extends RequestInit { queryParams?: Record<string, string> } function queryStringify(params: Record<string, string>): string { const query = new URLSearchParams(params) return query.toString() } // 抽离token获取逻辑,兼容双环境 async function getAccessToken() { if (typeof window === 'undefined') { // 服务端:动态导入next/headers,避免客户端加载 const { cookies } = await import('next/headers') return cookies().get('accessToken')?.value } else { // 客户端:解析document.cookie const cookieMatch = document.cookie.match(/accessToken=([^;]+)/) return cookieMatch ? decodeURIComponent(cookieMatch[1]) : null } } export async function fetcher(url: string, options?: FetchOptions, authorizedRequest?: boolean): Promise<any> { try { const queryParams = options?.queryParams ? `?${queryStringify(options.queryParams)}` : '' let headers: HeadersInit = { ...options?.headers, } if (authorizedRequest) { const accessToken = await getAccessToken() if (accessToken) { headers = { ...headers, 'Authorization': `Bearer ${accessToken}`, } } else { throw new Error('Access token is not available for an authorized request') } } if (options?.method === 'POST' || options?.method === 'PUT') { headers = { ...headers, 'Content-Type': 'application/json', } } const response = await fetch(`${BASE_API_URL}${url}${queryParams}`, { ...options, headers, }) if (!response.ok) { const status = response.status const errorStatusMap: Record<number, string> = { [HttpStatusCode.NotFound]: 'Not found', [HttpStatusCode.Unauthorized]: 'Unauthorized', [HttpStatusCode.Forbidden]: 'Forbidden', [HttpStatusCode.InternalServerError]: 'Internal server error', [HttpStatusCode.MethodNotAllowed]: 'Method not allowed', } const errorMessage = errorStatusMap[status] || response.statusText throw { status, message: errorMessage } } return await response.json() } catch (error) { throw error } }
解决方案2:将token作为参数传入fetcher(更推荐)
移除fetcher内部的token获取逻辑,让调用方根据自身环境自行获取token并传入,彻底避免环境冲突:
修改后的fetcher
import { HttpStatusCode } from 'axios' import { BASE_API_URL } from './axios' interface FetchOptions extends RequestInit { queryParams?: Record<string, string> } function queryStringify(params: Record<string, string>): string { const query = new URLSearchParams(params) return query.toString() } export async function fetcher(url: string, options?: FetchOptions, accessToken?: string): Promise<any> { try { const queryParams = options?.queryParams ? `?${queryStringify(options.queryParams)}` : '' let headers: HeadersInit = { ...options?.headers, } if (accessToken) { headers = { ...headers, 'Authorization': `Bearer ${accessToken}`, } } if (options?.method === 'POST' || options?.method === 'PUT') { headers = { ...headers, 'Content-Type': 'application/json', } } const response = await fetch(`${BASE_API_URL}${url}${queryParams}`, { ...options, headers, }) if (!response.ok) { const status = response.status const errorStatusMap: Record<number, string> = { [HttpStatusCode.NotFound]: 'Not found', [HttpStatusCode.Unauthorized]: 'Unauthorized', [HttpStatusCode.Forbidden]: 'Forbidden', [HttpStatusCode.InternalServerError]: 'Internal server error', [HttpStatusCode.MethodNotAllowed]: 'Method not allowed', } const errorMessage = errorStatusMap[status] || response.statusText throw { status, message: errorMessage } } return await response.json() } catch (error) { throw error } }
服务端调用示例(Server Components/API路由)
import { cookies } from 'next/headers' import { fetcher } from './fetcher' async function fetchServerData() { const token = cookies().get('accessToken')?.value if (!token) throw new Error('No access token') return fetcher('/user/profile', {}, token) }
客户端调用示例(客户端组件)
'use client' import { fetcher } from './fetcher' async function fetchClientData() { // 从document.cookie解析token const tokenMatch = document.cookie.match(/accessToken=([^;]+)/) const token = tokenMatch ? decodeURIComponent(tokenMatch[1]) : null if (!token) throw new Error('No access token') return fetcher('/user/profile', {}, token) }
解决方案3:使用Server Actions处理授权请求(最安全)
把授权请求逻辑放在Server Action中(服务端运行),客户端只需调用该Action,无需处理token:
创建Server Action
'use server' import { cookies } from 'next/headers' import { fetcher } from './fetcher' export async function fetchAuthorizedResource(url: string, options?: FetchOptions) { const token = cookies().get('accessToken')?.value if (!token) throw new Error('Access token missing') return fetcher(url, options, token) }
客户端组件调用
'use client' import { fetchAuthorizedResource } from './actions' async function handleFetch() { try { const data = await fetchAuthorizedResource('/user/profile') // 处理返回的数据 } catch (err) { // 处理错误 } } export default function ClientComponent() { return <button onClick={handleFetch}>获取用户信息</button> }
内容的提问来源于stack exchange,提问作者Кайрат Курбаналиев
相关产品推荐
相关产品推荐

