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

Next.js中Axios拦截器单次请求多次触发问题求助

Next.js中Axios拦截器多次触发的解决方案

问题原因

你遇到的拦截器多次调用问题,核心是拦截器被重复注册。不管是在_app.tsx还是index.tsx中,组件每次重渲染(比如路由切换、状态更新)时,顶层的拦截器注册代码都会重新执行,导致同一个拦截器被添加多次,单次请求/响应就会触发所有已注册的拦截器实例。

解决方案

方案一:创建独立的Axios实例(推荐)

单独维护Axios配置文件,确保拦截器只注册一次,避免组件重渲染带来的重复注册问题。

  1. 创建utils/axios.ts配置文件:
import axios, { AxiosRequestConfig } from 'axios';
import { toast } from 'react-toastify';
import authConfig from '../path/to/authConfig'; // 替换为你的authConfig实际路径

// 创建独立的Axios实例
const api = axios.create({
  baseURL: 'http://localhost:3333',
});

// 注册请求拦截器(仅执行一次)
api.interceptors.request.use(
  (config: AxiosRequestConfig) => {
    console.log('ok request');
    // 避免服务端渲染时访问window报错
    const accessToken = typeof window !== 'undefined' 
      ? window.localStorage.getItem(authConfig.storageTokenKeyName) 
      : null;

    if (accessToken && config.headers) {
      config.headers.Authorization = accessToken;
    }
    return config;
  },
  (error) => {
    console.log('ok, errors 1');
    return Promise.reject(error);
  }
);

// 注册响应拦截器(仅执行一次)
api.interceptors.response.use(
  (response) => {
    console.log('response >', response);
    return response;
  },
  (error) => {
    const { response } = error;

    // 仅在客户端环境处理路由跳转
    if (typeof window !== 'undefined') {
      const router = require('next/router').useRouter();
      if (response && response.status === 401) {
        router.push('/401');
      }
    }

    if (response?.data) {
      if (response.status === 500 || response.status === 400) {
        if (typeof response.data?.error === 'string') {
          toast.error(response.data.error, { duration: 3000 });
        } else {
          toast.error('Houve um erro desconhecido. Tente novamente ou contate o suporte!', { duration: 3000 });
        }
      } else if (response.status === 409) {
        if (response.data.message) {
          response.data.message.forEach((item: { message: string }) => {
            toast.error(item.message, { duration: 3000 });
          });
        } else {
          toast.error(response.data.error, { duration: 3000 });
        }
      }
    } else if (error?.code === "ERR_NETWORK") {
      toast.error('Ops! Houve de conexão. Tente novamente.', { duration: 3000 });
    } else {
      toast.error('Ops! Houve um erro desconhecido ao executar esta ação', { duration: 3000 });
    }
    console.log('ok, errors 2');
    return Promise.reject(error);
  }
);

export default api;
  1. 在组件中使用该实例:
import api from '../utils/axios';

// 示例请求
const fetchData = async () => {
  try {
    const res = await api.get('/your-api-path');
    // 处理返回数据
  } catch (err) {
    // 自定义错误处理(可选)
  }
};

方案二:在组件中用useEffect控制拦截器注册时机

如果必须在组件内处理,使用useEffect确保拦截器只注册一次,并在组件卸载时移除,防止内存泄漏和重复注册。

修改_app.tsx:

import { useEffect } from 'react';
import axios, { AxiosRequestConfig } from 'axios';
import { toast } from 'react-toastify';
import { useRouter } from 'next/router';
import authConfig from '../path/to/authConfig';

function MyApp({ Component, pageProps }) {
  const router = useRouter();

  useEffect(() => {
    axios.defaults.baseURL = 'http://localhost:3333';

    // 注册请求拦截器并保存引用
    const requestInterceptor = axios.interceptors.request.use(
      (config: AxiosRequestConfig) => {
        console.log('ok request');
        const accessToken = window.localStorage.getItem(authConfig.storageTokenKeyName);
        if (accessToken && config.headers) {
          config.headers.Authorization = accessToken;
        }
        return config;
      },
      (error) => {
        console.log('ok, errors 1');
        return Promise.reject(error);
      }
    );

    // 注册响应拦截器并保存引用
    const responseInterceptor = axios.interceptors.response.use(
      (response) => {
        console.log('response >', response);
        return response;
      },
      (error) => {
        const { response } = error;
        if (response && response.status === 401) {
          router.push('/401');
        }

        if (response?.data) {
          if (response.status === 500 || response.status === 400) {
            if (typeof response.data?.error === 'string') {
              toast.error(response.data.error, { duration: 3000 });
            } else {
              toast.error('Houve um erro desconhecido. Tente novamente ou contate o suporte!', { duration: 3000 });
            }
          } else if (response.status === 409) {
            if (response.data.message) {
              response.data.message.forEach((item: { message: string }) => {
                toast.error(item.message, { duration: 3000 });
              });
            } else {
              toast.error(response.data.error, { duration: 3000 });
            }
          }
        } else if (error?.code === "ERR_NETWORK") {
          toast.error('Ops! Houve de conexão. Tente novamente.', { duration: 3000 });
        } else {
          toast.error('Ops! Houve um erro desconhecido ao executar esta ação', { duration: 3000 });
        }
        console.log('ok, errors 2');
        return Promise.reject(error);
      }
    );

    // 组件卸载时移除拦截器
    return () => {
      axios.interceptors.request.eject(requestInterceptor);
      axios.interceptors.response.eject(responseInterceptor);
    };
  }, []); // 空依赖数组确保仅执行一次

  return <Component {...pageProps} />;
}

export default MyApp;

内容的提问来源于stack exchange,提问作者Renato Peres Clickativo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 20:30:50