无需从Server Components传递Cookie至API函数的实现方案
问题场景
目前在Next.js Server Components中,我可以通过next/headers的cookies()获取SessionId,再手动传递给API函数,以此让后端请求携带Cookie避免401错误。但每次调用API都要传SessionId太繁琐,尝试直接在api.ts里调用cookies()时,会触发以下错误:
Invariant: cookies() expects to have requestAsyncStorage, none available.
原因是Next.js的cookies()只能在有请求上下文的环境(比如Server Components、Server Actions、Route Handlers)中使用,而api.ts作为独立工具模块没有这个上下文,所以无法直接调用。
可行解决方案
方案1:创建带上下文的API实例工厂
不要定义全局axios实例,而是写一个工厂函数,接收SessionId生成已携带Cookie的API实例,在Server Components中初始化一次后,所有API调用复用该实例即可。
api.ts 代码修改:
import axios from 'axios'; // 工厂函数:根据SessionId创建API实例 export const createApiInstance = (sessionId: string) => { return axios.create({ baseURL: getBaseUrl(), headers: { Accept: 'application/hal+json', 'Content-Type': 'application/json', 'Accept-Version': '0.24.0.0', Cookie: `SESSIONID.DEV=${sessionId}`, }, withCredentials: true, }); }; // 通用请求函数,依赖传入的API实例 export const fetchData = async (id: string, apiInstance: ReturnType<typeof createApiInstance>) => { try { const response = await apiInstance.get(`/data/${id}`); return response.data; } catch (error) { console.error('Error fetching current asset', error); throw error; } };
Server Components 中使用:
import { cookies } from 'next/headers'; import { createApiInstance, fetchData } from './api'; const cookieStore = cookies(); const sessionId = cookieStore.get('SESSIONID.DEV')?.value; // 初始化一次API实例(注意处理SessionId为空的情况) const api = createApiInstance(sessionId!); const asset = await queryClient.fetchQuery({ queryKey: ['asset', params.id], queryFn: () => fetchData(params.id, api), }); console.log('Fetched asset:', asset);
方案2:用Server Action封装API调用
把API请求逻辑放到Server Action中,Server Action自带请求上下文,可直接调用cookies()获取SessionId,组件只需调用Action,无需手动传参。
actions.ts 代码:
'use server'; import { cookies } from 'next/headers'; import axios from 'axios'; const api = axios.create({ baseURL: getBaseUrl(), headers: { Accept: 'application/hal+json', 'Content-Type': 'application/json', 'Accept-Version': '0.24.0.0', }, withCredentials: true, }); // Server Action:封装数据请求逻辑 export async function fetchDataAction(id: string) { const sessionId = cookies().get('SESSIONID.DEV')?.value; try { const response = await api.get(`/data/${id}`, { headers: { Cookie: `SESSIONID.DEV=${sessionId}`, }, }); return response.data; } catch (error) { console.error('Error fetching current asset', error); throw error; } }
Server Components 中使用:
import { fetchDataAction } from './actions'; const asset = await queryClient.fetchQuery({ queryKey: ['asset', params.id], queryFn: () => fetchDataAction(params.id), }); console.log('Fetched asset:', asset);
方案3:用Route Handler做请求代理
创建Next.js的Route Handler作为中间层,在Handler中获取Cookie并转发请求到后端API,组件直接请求本地API即可,无需处理SessionId。
app/api/data/[id]/route.ts 代码:
import { cookies } from 'next/headers'; import axios from 'axios'; import { NextResponse } from 'next/server'; export async function GET(request: Request, { params }: { params: { id: string } }) { const sessionId = cookies().get('SESSIONID.DEV')?.value; try { const response = await axios.get(`${getBaseUrl()}/data/${params.id}`, { headers: { Accept: 'application/hal+json', 'Content-Type': 'application/json', 'Accept-Version': '0.24.0.0', Cookie: `SESSIONID.DEV=${sessionId}`, }, withCredentials: true, }); return NextResponse.json(response.data); } catch (error) { console.error('Error fetching current asset', error); return NextResponse.json({ error: 'Failed to fetch data' }, { status: 500 }); } }
Server Components 中使用:
const asset = await queryClient.fetchQuery({ queryKey: ['asset', params.id], queryFn: async () => { const res = await fetch(`/api/data/${params.id}`); if (!res.ok) throw new Error('Failed to fetch'); return res.json(); }, }); console.log('Fetched asset:', asset);
内容的提问来源于stack exchange,提问作者wyc

