Next.js 12.0.10中使用Axios实例、next-redux-wrapper实现授权时的数据获取问题求助
解决Next.js + Redux + Axios的Token注入与数据获取问题
看起来你碰到了Next.js中服务端/客户端环境差异带来的典型问题,我来帮你一步步拆解和解决这些问题:
核心问题分析
你遇到的两个问题本质都是服务端与客户端的运行环境差异导致的:
- 服务端渲染(SSR)或静态生成(SSG)时,
localStorage不存在,所以Axios实例初始化时无法获取用户Token; - Token刷新的拦截器逻辑没有区分环境,也没有和Redux状态、请求上下文联动。
1. 修复服务端数据获取的Token注入问题
首先要改掉Axios实例初始化时硬编码Authorization头的做法,改用请求拦截器动态注入Token,同时区分客户端和服务端的Token获取方式:客户端从localStorage拿,服务端从请求Cookie中拿(因为服务端无法访问localStorage,但可以获取请求携带的Cookie)。
改造Axios实例(axios.js)
import axios from "axios"; import cookie from "cookie"; // 先安装依赖:npm install cookie const baseURL = "你的API基础地址"; const axiosInstance = axios.create({ baseURL: baseURL, timeout: 20000, headers: { accept: "application/json", }, }); // 请求拦截器:动态注入Token axiosInstance.interceptors.request.use( (config) => { let token = null; if (typeof window !== "undefined") { // 客户端环境:从localStorage取Token token = localStorage.getItem("access_token"); } else { // 服务端环境:从请求Cookie中取Token if (config.headers?.cookie) { const parsedCookies = cookie.parse(config.headers.cookie); token = parsedCookies.access_token; } } if (token) { config.headers.Authorization = `JWT ${token}`; } return config; }, (error) => Promise.reject(error) ); // 响应拦截器:统一处理错误与Token刷新 axiosInstance.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config; // 处理401未授权,尝试刷新Token(避免重复刷新) if (error.response.status === 401 && !originalRequest._retry) { originalRequest._retry = true; try { let refreshToken = null; // 区分环境获取Refresh Token if (typeof window !== "undefined") { refreshToken = localStorage.getItem("refresh_token"); } else { if (error.config.headers?.cookie) { const parsedCookies = cookie.parse(error.config.headers.cookie); refreshToken = parsedCookies.refresh_token; } } // 调用刷新Token接口 const refreshRes = await axiosInstance.post("/api/auth/refresh/", { refresh: refreshToken, }); const newAccessToken = refreshRes.data.access; // 更新Token存储:客户端同步到localStorage和Cookie,方便服务端下次获取 if (typeof window !== "undefined") { localStorage.setItem("access_token", newAccessToken); document.cookie = `access_token=${newAccessToken}; path=/; max-age=3600`; } // 更新当前请求的Authorization头,重新发起请求 originalRequest.headers.Authorization = `JWT ${newAccessToken}`; return axiosInstance(originalRequest); } catch (refreshError) { // 刷新失败,清空Token并跳转到登录页 if (typeof window !== "undefined") { localStorage.removeItem("access_token"); localStorage.removeItem("refresh_token"); window.location.href = "/login"; } return Promise.reject(refreshError); } } return Promise.reject(error); } ); export default axiosInstance;
在数据获取方法中传递请求上下文
不管是getStaticProps还是getServerSideProps,都需要把请求上下文(ctx)传递给Thunk,让Axios能拿到服务端的Cookie:
改造Page.jsx的getStaticProps
export const getStaticProps = wrapper.getStaticProps((store) => async (ctx) => { try { // 把ctx传递给Thunk,用于服务端获取Cookie await store.dispatch(getCurrentUser(ctx)); } catch (e) { console.log("请求失败:", e); } return { props: {}, }; });
改造login-slice.js的getCurrentUser Thunk
export const getCurrentUser = createAsyncThunk( "auth/getCurrentUser", async (ctx, thunkApi) => { try { const requestConfig = {}; // 服务端环境下,把请求的Cookie放到请求头中 if (ctx?.req?.headers?.cookie) { requestConfig.headers = { cookie: ctx.req.headers.cookie, }; } const response = await axiosInstance.get("api/auth/user/", requestConfig); await thunkApi.dispatch(setCurrentUser(response.data)); return response.data; } catch (error) { if (error.response.data) { return thunkApi.rejectWithValue(error.response.data); } toast.error(error.message); return thunkApi.rejectWithValue(error.message); } } );
2. 是否必须始终使用getServerSideProps?
答案是不需要,要根据页面类型来选择:
- 如果是公共静态页面(比如首页、博客列表):可以继续用
getStaticProps,但如果页面需要用户专属数据,建议在客户端组件挂载后用useEffect调用getCurrentUser(客户端渲染); - 如果是用户专属页面(比如个人中心、订单页):必须用
getServerSideProps(因为每次请求都需要获取当前用户的Token,生成用户专属内容),或者纯客户端渲染(CSR)。
简单来说:
- 静态公共内容 →
getStaticProps+ 可选客户端用户数据渲染 - 用户专属内容 →
getServerSideProps或 纯客户端渲染
额外优化建议
- 把Token同时存在
localStorage和Cookie中:客户端用localStorage方便操作,服务端用Cookie获取Token; - 在Redux中持久化用户状态:避免重复发起获取用户信息的请求;
- 给Cookie设置
HttpOnly和Secure属性(生产环境):提升Token的安全性。
内容的提问来源于stack exchange,提问作者Elabbasy00
相关产品推荐
相关产品推荐

