Next.js中Axios拦截器单次请求多次触发问题求助
Next.js中Axios拦截器多次触发的解决方案
问题原因
你遇到的拦截器多次调用问题,核心是拦截器被重复注册。不管是在_app.tsx还是index.tsx中,组件每次重渲染(比如路由切换、状态更新)时,顶层的拦截器注册代码都会重新执行,导致同一个拦截器被添加多次,单次请求/响应就会触发所有已注册的拦截器实例。
解决方案
方案一:创建独立的Axios实例(推荐)
单独维护Axios配置文件,确保拦截器只注册一次,避免组件重渲染带来的重复注册问题。
- 创建
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;
- 在组件中使用该实例:
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
相关产品推荐
相关产品推荐

