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

Next.js 12.0.10中使用Axios实例、next-redux-wrapper实现授权时的数据获取问题求助

解决Next.js + Redux + Axios的Token注入与数据获取问题

看起来你碰到了Next.js中服务端/客户端环境差异带来的典型问题,我来帮你一步步拆解和解决这些问题:


核心问题分析

你遇到的两个问题本质都是服务端与客户端的运行环境差异导致的:

  1. 服务端渲染(SSR)或静态生成(SSG)时,localStorage不存在,所以Axios实例初始化时无法获取用户Token;
  2. 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 或 纯客户端渲染

额外优化建议

  1. 把Token同时存在localStorage和Cookie中:客户端用localStorage方便操作,服务端用Cookie获取Token;
  2. 在Redux中持久化用户状态:避免重复发起获取用户信息的请求;
  3. 给Cookie设置HttpOnly和Secure属性(生产环境):提升Token的安全性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 22:29:08