在NextJS与ApolloClient中如何通过Cookie实现身份认证?
核心问题分析
你遇到的本质问题是:
- SSR阶段(服务器端渲染):Apollo Client运行在服务器,无法直接访问浏览器Cookie,且绝对路径请求不会自动携带客户端Cookie
- 客户端阶段:需要相对路径请求才能让浏览器自动附带Cookie,同时要避免导入服务器端API(如
next/headers)导致的报错
下面是两种可行的优化方案,均遵循Next.js App Router和Apollo官方最佳实践:
方案一:利用makeClient上下文统一处理认证
直接在客户端组件的ApolloWrapper中,通过makeClient接收的上下文参数,在SSR时提取请求头中的Cookie,客户端则从document.cookie获取Token,统一注入Authorization头。
修改后的ApolloWrapper.ts
"use client"; import { ApolloLink, from, HttpLink } from "@apollo/client"; import { ApolloClient, ApolloNextAppProvider, InMemoryCache, } from "@apollo/client-integration-nextjs"; function makeClient(context?: { headers: Headers }) { // 通用认证链路:区分SSR/客户端场景提取Token const authLink = new ApolloLink((operation, forward) => { let token: string | undefined; if (typeof window === "undefined" && context?.headers) { // SSR场景:从请求头的Cookie中解析token const cookieHeader = context.headers.get("cookie"); if (cookieHeader) { token = cookieHeader.split("; ").find(c => c.startsWith("token="))?.split("=")[1]; } } else { // 客户端场景:从浏览器Cookie中解析token token = document.cookie.split("; ").find(c => c.startsWith("token="))?.split("=")[1]; } // 注入Authorization头 operation.setContext(({ headers }) => ({ headers: { ...headers, authorization: token ? `Bearer ${token}` : "", }, })); return forward(operation); }); const httpLink = new HttpLink({ uri: "/graphql", // 统一使用相对路径 credentials: "include", // 客户端请求自动携带Cookie }); return new ApolloClient({ cache: new InMemoryCache(), link: from([authLink, httpLink]), }); } export function ApolloWrapper({ children }: React.PropsWithChildren) { return ( <ApolloNextAppProvider makeClient={makeClient}> {children} </ApolloNextAppProvider> ); }
优化后的middleware.ts
import { type NextRequest, NextResponse } from "next/server"; export async function middleware(request: NextRequest) { if (request.nextUrl.pathname === "/graphql" && request.method === "POST") { const headers = new Headers(request.headers); // 优先使用Apollo注入的Authorization头, fallback到Cookie解析 if (!headers.has("Authorization")) { const token = request.cookies.get("token")?.value; if (token) { headers.set("Authorization", `Bearer ${token}`); } } // 转发到外部GraphQL服务 return NextResponse.rewrite(new URL("http://localhost:4000/gql"), { request: { ...request, headers, }, }); } return NextResponse.next(); } export const config = { matcher: ["/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)"], };
方案二:分离服务器/客户端Apollo配置
通过动态导入和条件导出,分别维护服务器端和客户端的Apollo链路,避免客户端代码依赖服务器API。
步骤1:创建共享配置文件apollo-shared.ts
// 无"use client"标记,可被两端导入 import { HttpLink, InMemoryCache } from "@apollo/client"; export const createHttpLink = (uri: string) => new HttpLink({ uri, credentials: "include", }); export const createCache = () => new InMemoryCache();
步骤2:服务器端Apollo配置apollo-server.ts
import { ApolloLink, from } from "@apollo/client"; import { ApolloClient, registerApolloClient } from "@apollo/client-integration-nextjs"; import { cookies } from "next/headers"; import { createCache, createHttpLink } from "./apollo-shared"; const authLink = new ApolloLink((operation, forward) => { operation.setContext(async () => { const cookieStore = await cookies(); const token = cookieStore.get("token")?.value; return { headers: { authorization: token ? `Bearer ${token}` : "", }, }; }); return forward(operation); }); const httpLink = createHttpLink("http://localhost:4000/gql"); export const { getClient, query, PreloadQuery } = registerApolloClient(() => { return new ApolloClient({ cache: createCache(), link: from([authLink, httpLink]), }); });
步骤3:客户端Apollo配置ApolloWrapper.ts
"use client"; import { ApolloLink, from } from "@apollo/client"; import { ApolloClient, ApolloNextAppProvider } from "@apollo/client-integration-nextjs"; import { createCache, createHttpLink } from "./apollo-shared"; function makeClient() { const authLink = new ApolloLink((operation, forward) => { // 客户端直接从浏览器Cookie提取Token const token = document.cookie.split("; ").find(c => c.startsWith("token="))?.split("=")[1]; operation.setContext(({ headers }) => ({ headers: { ...headers, authorization: token ? `Bearer ${token}` : "", }, })); return forward(operation); }); const httpLink = createHttpLink("/graphql"); return new ApolloClient({ cache: createCache(), link: from([authLink, httpLink]), }); } export function ApolloWrapper({ children }: React.PropsWithChildren) { return ( <ApolloNextAppProvider makeClient={makeClient}> {children} </ApolloNextAppProvider> ); }
关键注意点
- SSR阶段的Cookie获取:必须通过请求头传递,服务器端无法直接访问浏览器的Cookie存储
- 相对路径的作用:客户端使用相对路径
/graphql,结合credentials: "include",浏览器会自动附带当前域名的Cookie - 避免跨环境API调用:客户端组件(标记
"use client")绝对不能导入next/headers等服务器端专属API
内容的提问来源于stack exchange,提问作者Ty Nielson
相关产品推荐
相关产品推荐

