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

Next.js 13页面加载优化:实现路由切换时稳定显示Spinner

Next.js 13 路由切换加载动画不稳定问题排查与解决建议

问题现象

从首页导航至/example页面时,存在两种不一致表现:

  • A. 页面切换缓慢(约2-3秒),直接展示目标页面内容,无加载动画,用户体验差
  • B. 路由立即切换,加载动画在内容就绪前稳定显示,符合预期行为

当前核心需求

使用Firebase获取数据,暂不优化数据获取逻辑,优先实现路由即时切换+稳定显示加载动画,明确提示用户内容加载中。

已尝试方案

  • 实现代码分割与动态导入,降低初始加载体积
  • 在/app目录下创建loading.tsx全局加载组件

相关代码

layout.tsx

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en" suppressHydrationWarning>
      <head />
      <body>
        <AppLayout>
          <MainContainer>{children}</MainContainer>
        </AppLayout>
      </body>
    </html>
  );
}

MainContainer.tsx

const MainContainer: React.FC<MainContainerProps> = ({ children }) => {
  const color = useColorModeValue(light, dark);

  return (
    <Grid
      templateAreas={`"header""module""footer"`}
      gridTemplateRows="min-content 1fr min-content"
      gridTemplateColumns="1fr"
      minH="100vh"
      bg={color.background}
      color={color.onBackground}
      outlineColor={color.outline}
    >
      <GridItem area="header" position="sticky" top="0" zIndex={99}>
        <Header />
      </GridItem>
      <GridItem area="module" position="relative" overflow="hidden">
        <Suspense
          fallback={
            <Center minH="calc(100vh - 4rem)">
              <Spinner color={color.primary} />
            </Center>
          }
        >
          {children}
        </Suspense>
      </GridItem>
      <GridItem area="footer">
        <Footer />
      </GridItem>
    </Grid>
  );
};

解决建议

1. 确保页面组件的异步数据获取被Suspense捕获

Next.js 13 App Router中,Suspense只有在组件内部存在顶层async/await数据获取时才会触发fallback。检查/example页面组件:

  • 必须将Firebase数据请求放在组件顶层(而非useEffect),用async函数包裹:
// app/example/page.tsx
async function ExamplePage() {
  // 顶层异步数据获取,让Suspense能捕获加载状态
  const data = await fetchFirebaseData();
  return <div>{data.content}</div>;
}
export default ExamplePage;

2. 避免Suspense层级冲突

你在MainContainer中嵌套了Suspense,同时又使用了App Router的loading.tsx,可能导致状态冲突:

  • 移除MainContainer中的Suspense,改用页面级或路由组的loading.tsx:
    在app/example目录下创建专属loading.tsx,确保路由切换时立即显示该组件:
// app/example/loading.tsx
export default function Loading() {
  const color = useColorModeValue(light, dark);
  return (
    <Center minH="calc(100vh - 4rem)">
      <Spinner color={color.primary} />
    </Center>
  );
}

3. 禁用路由预取

Next.js默认会预取可视区域内的链接,导致页面提前加载,跳过加载动画:

  • 在Link组件中添加prefetch={false}:
<Link href="/example" prefetch={false}>
  跳转到Example页面
</Link>

4. 手动控制加载状态(备选方案)

如果上述方法无效,可通过状态管理手动控制加载动画:

  • 在导航时设置全局加载状态,数据加载完成后关闭:
// 示例:用Context管理加载状态
const LoadingContext = createContext(false);

// 在RootLayout中提供状态
export default function RootLayout({ children }) {
  const [isLoading, setIsLoading] = useState(false);
  return (
    <LoadingContext.Provider value={{ isLoading, setIsLoading }}>
      {/* ...原有布局 */}
      {isLoading && <SpinnerOverlay />}
    </LoadingContext.Provider>
  );
}

// 自定义Link组件
function CustomLink({ href, children }) {
  const { setIsLoading } = useContext(LoadingContext);
  const router = useRouter();

  const handleClick = async (e) => {
    e.preventDefault();
    setIsLoading(true);
    await router.push(href);
    setIsLoading(false);
  };

  return <a href={href} onClick={handleClick}>{children}</a>;
}

内容的提问来源于stack exchange,提问作者Tom Fan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.22 12:42:51