Next.js使用useSuspenseQuery遇401未授权问题求助
问题分析与解决方案
核心原因
刷新页面触发Next.js服务端渲染(SSR)时,useSuspenseQuery会在服务端执行查询逻辑:
- 服务端无法访问浏览器的
localStorage/sessionStorage,导致axiosWithAuth请求未携带有效accessToken,直接触发401。 - 拦截器中刷新token的逻辑依赖客户端存储的
refreshToken,服务端无法获取,重试请求失败后抛出错误。 - 路由切换属于客户端导航,所有逻辑在浏览器执行,能正常读取存储的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并添加到请求头:
- 修改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; });
- 刷新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,提问作者И И
相关产品推荐
相关产品推荐

