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

Next.js中Axios拦截器用useRouter触发无效Hook调用求助

解决Next.js 14中Axios拦截器捕获401时的导航问题

问题核心

你遇到的三个问题本质原因如下:

  • useRouter无效Hook调用:useRouter是React Hook,必须遵循Hook规则——只能在组件或自定义Hook的顶层调用,且不能在异步回调、拦截器这类非Hook/组件的逻辑中直接使用。另外你的AxiosInterceptorInstance没有以use开头,React不会将其识别为自定义Hook,调用时也会触发Hook规则报错。
  • redirect方法无效:Next.js的redirect仅适用于Server Components、路由处理函数(如page.tsx的Server Component)或Server Actions,客户端拦截器属于客户端运行时,无法触发该方法。
  • window.location无限循环:跳转到/login后,若页面仍会发起需要权限的请求(比如布局组件里的请求),会再次触发401拦截,导致循环跳转。

可行解决方案

方案1:基于客户端组件的Axios拦截器(推荐)

将Axios实例的创建和拦截器逻辑放在客户端组件中,用useRef保存实例避免重复创建,同时在跳转前判断当前路径,防止循环:

'use client';
import axios from "axios";
import { useRouter, usePathname } from "next/navigation";
import toast from "react-hot-toast";
import { useEffect, useRef } from "react";

// 自定义Hook,必须以use开头
export const useAxiosInterceptor = () => {
  const router = useRouter();
  const pathname = usePathname();
  const axiosInstanceRef = useRef(null);

  useEffect(() => {
    // 只创建一次Axios实例
    if (!axiosInstanceRef.current) {
      const instance = axios.create({
        baseURL: process.env.NEXT_PUBLIC_API_URL,
      });

      // 请求拦截器
      instance.interceptors.request.use(
        (config) => {
          const accessToken = localStorage.getItem("auth_token");
          if (accessToken && config.headers) {
            config.headers.Authorization = `Bearer ${accessToken}`;
          }
          return config;
        },
        (error) => Promise.reject(error)
      );

      // 响应拦截器
      instance.interceptors.response.use(
        (response) => response,
        (error) => {
          if (error.response?.status === 401) {
            toast.error(error.response.data.message, { duration: 4500 });
            // 判断当前路径不是/login时再跳转,避免循环
            if (pathname !== "/login") {
              router.push("/login");
              // 可选:清除无效token
              localStorage.removeItem("auth_token");
            }
          } else if (error.response?.status === 422) {
            toast.error(error.response.data.errors[0], { duration: 2500 });
          } else if (error.response?.status === 500) {
            toast.error("服务器出错", { duration: 2500 });
          } else {
            toast.error(`${error.response?.statusText || "请求失败"} | ${error.message}`);
          }
          return Promise.reject(error);
        }
      );

      axiosInstanceRef.current = instance;
    }

    // 组件卸载时移除拦截器
    return () => {
      if (axiosInstanceRef.current) {
        axiosInstanceRef.current.interceptors.request.clear();
        axiosInstanceRef.current.interceptors.response.clear();
      }
    };
  }, [router, pathname]);

  return axiosInstanceRef.current;
};

使用方式:在客户端组件中调用该Hook获取Axios实例

'use client';
import { useAxiosInterceptor } from '@/utils/axios-interceptor';

const MyComponent = () => {
  const axiosInstance = useAxiosInterceptor();

  const fetchData = async () => {
    try {
      const res = await axiosInstance.get("/api/data");
      // 处理数据
    } catch (err) {
      // 错误已被拦截器处理
    }
  };

  return <button onClick={fetchData}>获取数据</button>;
};

方案2:纯客户端全局导航(无Hook依赖)

如果不想依赖React Hook,可以直接用window.location但加严格的路径判断,同时清除无效token避免重复触发401:

// 响应拦截器中修改401逻辑
case 401:
  toast.error(error.response.data.message, { duration: 4500 });
  localStorage.removeItem("auth_token");
  // 只有当前路径不是/login时才跳转
  if (window.location.pathname !== "/login") {
    window.location.href = "/login";
  }
  break;

注意:这种方式适合全局Axios实例,但要确保/login页面不会发起需要权限的请求,否则仍可能循环。

关键注意点

  • 自定义Hook必须以use开头,确保React识别并遵循Hook规则。
  • 用useRef保存Axios实例,避免组件重新渲染时重复创建实例和拦截器。
  • 跳转前判断当前路径,是解决无限循环的核心。
  • 客户端代码必须加'use client'指令(Next.js 13+ App Router)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 18:53:13