Next.js Suspense切换路由时旧UI闪现问题的排查与解决
问题:Next.js路由切换时Suspense骨架屏异常显示旧数据
背景
在Next.js应用的/organization/:orgId/activity路由中,我使用Suspense实现内容加载完成前显示骨架屏的效果。页面采用Server Components,ActivityList组件通过Prisma从PlanetScale数据库获取对应组织的活动日志数据。
异常现象
当从一个/orgId/activity路由切换到另一个时,页面会出现不符合预期的加载流程:
- 先显示Suspense的骨架屏fallback
- 短暂显示上一页的旧活动列表UI
- 旧UI消失,新活动列表加载完成并显示
该问题在所有同类组件中重复出现。
相关代码
页面组件(ActivityPage)
const ActivityPage = async ({ params }: Props) => { const isSubscriped = await checkSubscription() return ( <div className="w-full space-y-2"> <Info isSubscriped={isSubscriped} /> <Separator /> <Suspense key={params.organizationId} fallback={<ActivityList.Skeleton />}> <ActivityList /> </Suspense> </div> ) } export default ActivityPage
ActivityList组件及骨架屏
import { ActivityItem } from "@/components/activity-item" import { Skeleton } from "@/components/ui/skeleton" import { db } from "@/lib/db" import { auth } from "@clerk/nextjs" import { redirect } from "next/navigation" export const ActivityList = async () => { const { orgId } = auth() if (!orgId) { redirect("select-org") } const logs = await db.auditLog.findMany({ where: { orgId, }, orderBy: { createdAt: "desc" } }) return ( <ol className="space-y-4 mt-4"> {logs.length ? ( logs.map((log) => <ActivityItem key={log.id} auditLog={log} />) ) : ( <p className="block text-muted-foreground text-center text-sm">No activity found in this organization</p> )} </ol> ) } ActivityList.Skeleton = function() { return ( <ol className="space-y-4 mt-4"> <div className="w-[75%] flex items-start gap-2"> <Skeleton className="w-8 h-8 bg-neutral-200" /> <Skeleton className="w-[75%] h-8 bg-neutral-200"/> </div> <div className="w-[75%] flex items-start gap-2"> <Skeleton className="w-8 h-8 bg-neutral-200" /> <Skeleton className="w-[60%] h-8 bg-neutral-200"/> </div> <div className="w-[75%] flex items-start gap-2"> <Skeleton className="w-8 h-8 bg-neutral-200" /> <Skeleton className="w-[75%] h-8 bg-neutral-200"/> </div> <div className="w-[75%] flex items-start gap-2"> <Skeleton className="w-8 h-8 bg-neutral-200" /> <Skeleton className="w-[55%] h-8 bg-neutral-200"/> </div> <div className="w-[75%] flex items-start gap-2"> <Skeleton className="w-8 h-8 bg-neutral-200" /> <Skeleton className="w-[80%] h-8 bg-neutral-200"/> </div> </ol> ) }
尝试过的无效方案
- 将数据获取逻辑移到页面组件中
- 在未获取到
logs时返回null - 搜索网络上的相关解决方案
- 手动延迟Promise模拟加载
以上方法均未彻底解决问题,部分操作还引发了Hydration错误。
最终解决方案
经排查,问题根源是Clerk的组织ID未在路由切换时及时更新,导致ActivityList组件获取到的仍是上一个组织的ID,从而加载旧数据。通过在路由切换前调用setActive方法同步当前组织ID,解决了该问题:
const onRouteChange = async (href: string) => { await setActive?.({organization: organization.id}) router.push(href) }
内容的提问来源于stack exchange,提问作者Patrick
相关产品推荐
相关产品推荐

