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

在TRPC Next.js客户端fetch处理器中获取Cookies的问题

解决Next.js + TRPC客户端无法获取Cookie的令牌刷新问题

核心问题分析

  • 你的代码设置了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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 01:05:25