在TRPC Next.js客户端fetch处理器中获取Cookies的问题
核心问题分析
- 你的代码设置了
ssr: false,因此config({ctx})中的上下文ctx会是undefined,因为SSR渲染上下文不存在。 js-cookie依赖浏览器DOM环境,但TRPC的config初始化代码运行在模块层面,此时document对象可能未就绪,导致无法正常读取Cookie。
解决方案
1. 直接在fetch函数中用原生API读取Cookie
由于应用关闭了SSR,客户端运行时document对象完全可用,可直接解析Cookie:
// 在httpBatchLink的fetch方法中添加Cookie解析逻辑 fetch(url, options) { // 封装Cookie读取函数 const getCookie = (name: string) => { const value = `; ${document.cookie}`; const parts = value.split(`; ${name}=`); if (parts.length === 2) return parts.pop()?.split(';').shift(); }; const refreshToken = getCookie('refreshToken'); // 后续可基于refreshToken调用刷新接口处理过期逻辑 return fetch(url, { ...options, credentials: "include", }); }
2. 用TRPC链路中间件实现自动令牌刷新
更优雅的方式是添加错误处理链路,捕获401错误后自动刷新令牌并重试请求:
import { httpBatchLink, loggerLink, TRPCLink } from "@trpc/client"; import { TRPC_ERROR_CODES } from "@trpc/server"; // 自定义认证错误处理链路 const authLink: TRPCLink<AppRouter> = () => { return ({ next, op }) => { return next(op).catch(async (error) => { // 捕获访问令牌过期的401错误 if (error.data?.code === TRPC_ERROR_CODES.UNAUTHORIZED) { // 读取refreshToken const getCookie = (name: string) => { const value = `; ${document.cookie}`; const parts = value.split(`; ${name}=`); if (parts.length === 2) return parts.pop()?.split(';').shift(); }; const refreshToken = getCookie('refreshToken'); if (!refreshToken) throw error; // 无刷新令牌直接抛出错误 // 调用刷新令牌的API路由 const refreshRes = await fetch('/api/refresh-token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refreshToken }), credentials: 'include', }); if (!refreshRes.ok) throw error; const { newAccessToken } = await refreshRes.json(); setToken(newAccessToken); // 更新全局访问令牌 // 重试原请求,携带新令牌 return next({ ...op, ctx: { ...op.ctx, headers: { ...op.ctx?.headers, Authorization: `Bearer ${newAccessToken}`, }, }, }); } throw error; // 非401错误直接抛出 }); }; }; // 更新TRPC配置,添加认证链路 export const api = createTRPCNext<AppRouter>({ config({ctx}) { return { links: [ loggerLink({/* 你的日志配置 */}), authLink, // 错误处理链路放在http链路之前 httpBatchLink({ transformer: superjson, url: `${getBaseUrl()}/api/trpc`, headers: () => { const newHeaders = new Headers(); if(token) newHeaders.set("Authorization", `Bearer ${token}`); return newHeaders; }, fetch(url, options) { return fetch(url, { ...options, credentials: "include", }); }, }), ], }; }, ssr: false, transformer: superjson, });
3. 安全优化建议
如果refreshToken设置了httpOnly: true(更安全,防止XSS窃取),客户端JS无法直接读取,此时可以调整刷新接口逻辑:让/api/refresh-token直接从请求Cookie中读取refreshToken,客户端无需传递,只需要调用接口即可,进一步提升安全性。
内容的提问来源于stack exchange,提问作者KlautNet
相关产品推荐
相关产品推荐

