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

Next.js使用useSuspenseQuery遇401未授权问题求助

问题分析与解决方案

核心原因

刷新页面触发Next.js服务端渲染(SSR)时,useSuspenseQuery会在服务端执行查询逻辑:

  1. 服务端无法访问浏览器的localStorage/sessionStorage,导致axiosWithAuth请求未携带有效accessToken,直接触发401。
  2. 拦截器中刷新token的逻辑依赖客户端存储的refreshToken,服务端无法获取,重试请求失败后抛出错误。
  3. 路由切换属于客户端导航,所有逻辑在浏览器执行,能正常读取存储的token,因此无问题;useQuery默认仅在客户端执行(除非主动配置服务端预取),所以也不会触发该错误。

另外注意到你拦截器代码里的拼写错误:jwt expried应该改为jwt expired,这会导致token过期的判断逻辑失效,需要优先修正。


解决方案

方案1:限制useSuspenseQuery仅在客户端执行

通过enabled选项判断运行环境,让查询只在浏览器中执行,避免服务端无token的情况:

import { useSuspenseQuery } from '@tanstack/react-query';

// 判断是否在浏览器环境
const isBrowser = typeof window !== 'undefined';

const { data } = useSuspenseQuery({
  queryKey: ["user"],
  queryFn: () => userService.getUserById(Number(id)),
  enabled: isBrowser, // 仅客户端启用查询
});

如果组件不需要服务端渲染,也可以用Next.js的dynamic组件禁用SSR:

import dynamic from 'next/dynamic';

// 禁用SSR,组件仅在客户端渲染
const UserProfile = dynamic(() => import('./UserProfile'), { ssr: false });

方案2:服务端通过Cookie传递授权信息

如果将token存储在HttpOnly Cookie中(而非localStorage),服务端可以直接读取Cookie并添加到请求头:

  1. 修改axios请求拦截器:
import axios from 'axios';
import { cookies } from 'next/headers';

const axiosWithAuth = axios.create({
  baseURL: process.env.NEXT_PUBLIC_API_BASE_URL,
});

axiosWithAuth.interceptors.request.use((config) => {
  let token;
  if (typeof window !== 'undefined') {
    // 客户端从Cookie读取(或保持原逻辑从storage读取)
    token = document.cookie.split('; ').find(row => row.startsWith('accessToken='))?.split('=')[1];
  } else {
    // 服务端从Next.js Cookie存储读取
    const cookieStore = cookies();
    token = cookieStore.get('accessToken')?.value;
  }
  if (token) {
    config.headers.Authorization = `Bearer ${token}`;
  }
  return config;
});
  1. 刷新token后同步更新Cookie:
    在authService.getNewTokens成功后,不仅更新localStorage,还要设置Cookie:
async function getNewTokens() {
  const refreshToken = localStorage.getItem('refreshToken');
  const response = await axios.post('/api/auth/refresh', { refreshToken });
  const { accessToken } = response.data;
  
  // 更新localStorage
  localStorage.setItem('accessToken', accessToken);
  // 设置Cookie(可根据需求调整secure、sameSite等属性)
  document.cookie = `accessToken=${accessToken}; path=/; max-age=3600; secure; sameSite=strict`;
  
  return { accessToken };
}

方案3:调整拦截器,服务端跳过token刷新逻辑

在拦截器中判断运行环境,仅在客户端尝试刷新token,避免服务端因无法获取refreshToken而重试失败:

axiosWithAuth.interceptors.response.use(
  (config) => config,
  async (error) => {
    const originalRequest = error.config;
    const isBrowser = typeof window !== 'undefined';

    // 仅在客户端执行token刷新逻辑
    if (
      isBrowser &&
      (error?.response?.status === 401 ||
        errorCatch(error) === "jwt expired" || // 修正拼写错误
        errorCatch(error) === "jwt must be provided") &&
      error.config &&
      !error.config._isRetry
    ) {
      originalRequest._isRetry = true;
      try {
        const { accessToken } = await authService.getNewTokens();
        originalRequest.headers["Authorization"] = `Bearer ${accessToken}`;
        return axiosWithAuth.request(originalRequest);
      } catch (error) {
        if (errorCatch(error) === "jwt expired") removeFromStorage();
      }
    }

    throw error;
  }
);

内容的提问来源于stack exchange,提问作者И И

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 15:50:10