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> ); }
预期点击按钮后:
- 地址栏立即更新URL
- 显示Suspense的fallback加载态
- 服务器组件加载完成后展示新内容
但实际行为是:服务器组件加载期间页面无变化,加载完成后地址栏才更新并渲染内容,Suspense未生效。
问题原因
Next.js在客户端执行同路径下的searchParams修改时,默认会先完成服务器组件的数据加载,再同步更新地址栏和DOM,导致Suspense的fallback无法触发展示。
解决方案
方案一:用startTransition包裹导航操作
通过React的startTransition将导航标记为非紧急过渡任务,让React优先展示Suspense的fallback,同时后台进行服务器组件的加载。
- 修改
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> ); }
- 修改
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> ); }
- 修改
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
相关产品推荐
相关产品推荐

