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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 16:05:31