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

Next.js中useTransition期间Link组件失效及API调用异常的最佳实践

Next.js中Link跳转与useTransition共存的最佳实践

核心问题分析

  • useTransition 触发的过渡状态会阻塞Next.js的路由跳转逻辑,Next.js默认会等待所有pending的过渡完成后才执行路由切换
  • API调用被取消是因为路由跳转时React会中断所有未完成的pending过渡任务,导致API请求被abort,过渡状态永远停留在isPending: true

解决方案与最佳实践

1. 手动控制过渡与路由跳转的顺序

放弃Link的自动跳转逻辑,改为手动触发路由,确保过渡完成后再执行跳转:

import { useTransition, useRouter } from 'next/navigation';
import { useState } from 'react';

function MyComponent() {
  const router = useRouter();
  const [isPending, startTransition] = useTransition();
  const [apiData, setApiData] = useState(null);

  const handleNavigate = () => {
    startTransition(async () => {
      try {
        // 执行服务端API调用
        const res = await fetch('/api/your-endpoint');
        const data = await res.json();
        setApiData(data);
        // 过渡完成后手动触发路由跳转
        router.push('/target-page');
      } catch (err) {
        // 处理错误,避免过渡卡住
        console.error(err);
      }
    });
  };

  return (
    <button onClick={handleNavigate} disabled={isPending}>
      {isPending ? '处理中...' : '跳转并执行操作'}
    </button>
  );
}

2. 隔离API调用与过渡状态

如果API调用不需要直接绑定UI过渡,可将请求放在过渡之外,通过状态触发后续的过渡与跳转:

import { useTransition, useRouter } from 'next/navigation';
import { useState, useEffect } from 'react';

function MyComponent() {
  const router = useRouter();
  const [isPending, startTransition] = useTransition();
  const [shouldCallApi, setShouldCallApi] = useState(false);
  const [apiData, setApiData] = useState(null);

  useEffect(() => {
    if (shouldCallApi) {
      const fetchData = async () => {
        try {
          const res = await fetch('/api/your-endpoint');
          const data = await res.json();
          setApiData(data);
          // API完成后触发过渡和跳转
          startTransition(() => {
            router.push('/target-page');
          });
        } catch (err) {
          console.error(err);
          setShouldCallApi(false);
        }
      };
      fetchData();
    }
  }, [shouldCallApi, router, startTransition]);

  const handleNavigate = () => {
    setShouldCallApi(true);
  };

  return (
    <button onClick={handleNavigate} disabled={isPending || shouldCallApi}>
      {isPending || shouldCallApi ? '处理中...' : '跳转并执行操作'}
    </button>
  );
}

3. 处理API请求的取消逻辑

如果必须在过渡中执行API,需要手动管理请求的取消与过渡状态的重置,避免过渡卡住:

import { useTransition, useRouter } from 'next/navigation';
import { useState, useRef } from 'react';

function MyComponent() {
  const router = useRouter();
  const [isPending, startTransition] = useTransition();
  const abortControllerRef = useRef(new AbortController());

  const handleNavigate = () => {
    abortControllerRef.current = new AbortController();
    startTransition(async () => {
      try {
        const res = await fetch('/api/your-endpoint', {
          signal: abortControllerRef.current.signal
        });
        const data = await res.json();
        // 处理返回数据
        router.push('/target-page');
      } catch (err) {
        // 排除用户主动取消的情况
        if (err.name !== 'AbortError') {
          console.error(err);
        }
      }
    });
  };

  return (
    <>
      <button onClick={handleNavigate} disabled={isPending}>
        {isPending ? '处理中...' : '跳转并执行操作'}
      </button>
      <link rel="prefetch" href="/target-page" />
    </>
  );
}

4. 利用Next.js路由事件拦截

通过useRouter的路由事件拦截,确保服务端操作完成后再允许路由跳转:

import { useTransition, useRouter } from 'next/navigation';
import { useState, useEffect } from 'react';

function MyComponent() {
  const router = useRouter();
  const [isPending, startTransition] = useTransition();
  const [isProcessing, setIsProcessing] = useState(false);

  useEffect(() => {
    const handleRouteChangeStart = (url: string) => {
      if (isProcessing) {
        // 阻止默认路由跳转
        router.events.emit('routeChangeError', new Error('Processing in progress'), url);
        // 执行完操作后再跳转
        startTransition(async () => {
          await fetch('/api/your-endpoint');
          setIsProcessing(false);
          router.push(url);
        });
      }
    };

    router.events.on('routeChangeStart', handleRouteChangeStart);
    return () => {
      router.events.off('routeChangeStart', handleRouteChangeStart);
    };
  }, [router, isProcessing, startTransition]);

  const handleNavigate = () => {
    setIsProcessing(true);
    router.push('/target-page');
  };

  return (
    <button onClick={handleNavigate} disabled={isPending || isProcessing}>
      {isPending || isProcessing ? '处理中...' : '跳转并执行操作'}
    </button>
  );
}

关键注意事项

  • 避免在useTransition的回调中直接使用Link组件,其跳转逻辑会被过渡状态阻塞
  • 所有异步操作必须添加错误处理,未捕获的异常会导致过渡状态永远无法结束
  • 若API调用耗时较长,建议添加加载状态提示,提升用户体验

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 22:55:11