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

NextJS 13中useRouter跳转时Suspense fallback不显示求助

Next.js修改SearchParams时Suspense Fallback不显示的解决方案

问题场景

我通过useRouter钩子修改路由的searchParams,编写了如下极简示例代码:

// page.tsx
import { Suspense } from "react";
import ClientComponent from "./client-component";
import ServerComponent from "./server-component";

export default function Page() {
  return (
    <main>
      <ClientComponent />
      <Suspense fallback={<div>Loading...</div>}>
        <ServerComponent />
      </Suspense>
    </main>
  );
}

// server-component.tsx
export default async function ServerComponent() {
  const datetime = new Date().toLocaleString();
  await new Promise((resolve) => setTimeout(resolve, 2000));
  return <div>Server component rendered at {datetime}</div>;
}

// client-component.tsx
'use client';

import { useRouter } from "next/navigation";

export default function ClientComponent() {
  const router = useRouter();

  function handleOnClick() {
    const random = Math.random().toString(36).substring(7);
    router.push(`/min?q=${random}`);
  }

  return (
    <button onClick={handleOnClick}>change route</button>
  );
}

预期点击按钮后:

  1. 地址栏立即更新URL
  2. 显示Suspense的fallback加载态
  3. 服务器组件加载完成后展示新内容

但实际行为是:服务器组件加载期间页面无变化,加载完成后地址栏才更新并渲染内容,Suspense未生效。

问题原因

Next.js在客户端执行同路径下的searchParams修改时,默认会先完成服务器组件的数据加载,再同步更新地址栏和DOM,导致Suspense的fallback无法触发展示。

解决方案

方案一:用startTransition包裹导航操作

通过React的startTransition将导航标记为非紧急过渡任务,让React优先展示Suspense的fallback,同时后台进行服务器组件的加载。

  1. 修改client-component.tsx:
'use client';

import { useRouter } from "next/navigation";
import { startTransition } from "react";

export default function ClientComponent() {
  const router = useRouter();

  function handleOnClick() {
    const random = Math.random().toString(36).substring(7);
    // 用startTransition包裹导航操作
    startTransition(() => {
      router.push(`/min?q=${random}`);
    });
  }

  return (
    <button onClick={handleOnClick}>change route</button>
  );
}
  1. 修改page.tsx,将searchParams传递给服务器组件(明确依赖关系,确保参数变化时触发重新渲染):
import { Suspense } from "react";
import ClientComponent from "./client-component";
import ServerComponent from "./server-component";

export default function Page({ searchParams }: { searchParams: { q?: string } }) {
  return (
    <main>
      <ClientComponent />
      <Suspense fallback={<div>Loading...</div>}>
        <ServerComponent q={searchParams.q} />
      </Suspense>
    </main>
  );
}
  1. 修改server-component.tsx接收参数:
export default async function ServerComponent({ q }: { q?: string }) {
  const datetime = new Date().toLocaleString();
  await new Promise((resolve) => setTimeout(resolve, 2000));
  return <div>Server component rendered at {datetime} (q={q})</div>;
}

方案二:手动控制加载状态 + 先更新地址栏再刷新

如果方案一不生效,可以手动控制加载状态,先更新地址栏,再触发服务器组件刷新:

修改client-component.tsx:

'use client';

import { useRouter } from "next/navigation";
import { useState } from "react";

export default function ClientComponent() {
  const router = useRouter();
  const [isLoading, setIsLoading] = useState(false);

  async function handleOnClick() {
    const random = Math.random().toString(36).substring(7);
    // 先更新地址栏
    await router.replace(`/min?q=${random}`);
    setIsLoading(true);
    try {
      // 触发服务器组件重新渲染
      await router.refresh();
    } finally {
      setIsLoading(false);
    }
  }

  return (
    <>
      {/* 手动展示加载态 */}
      {isLoading && <div>Loading...</div>}
      <button onClick={handleOnClick} disabled={isLoading}>change route</button>
    </>
  );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 20:09:54