Next.js 13(App目录)加载页实现问题求助
解决Next.js App目录模式下加载页不显示问题
我是Next.js新手,采用App目录模式开发大型网站,为提升用户体验需实现加载页。已创建Loading组件,先后尝试通过useState+useEffect、React Suspense两种方式配置,但加载页始终不显示且无报错,相关代码如下:
初始Layout代码
import "./globals.css"; import React, { useState, useEffect } from "react"; import Loading from "../components/loading/loading"; const Layout = ({ children, dataLoaded }) => { const [loading, setLoading] = useState(true); useEffect(() => { if (dataLoaded) { setLoading(false); } }, [dataLoaded]); return ( <body className="app {oswald.className}"> {loading && <Loading />} {children} </body> ); }; export default Layout;
尝试方案1代码
layout.jsx
import "./globals.css"; import { Suspense } from "react"; import Loading from "../components/loading/loading"; export default function RootLayout({ children }) { return ( <html lang="en"> <head /> <body> <Suspense fallback={<Loading />}>{children}</Suspense> </body> </html> ); }
LoadingPage.js
const LoadingPage = () => { return ( <div className="loading w-screen h-screen bg-red-100"> <p>Loading...</p> </div> ); }; export default LoadingPage;
Loading.js
import LoadingPage from "@/components/loading/loading"; export default function Loading() { return <LoadingPage />; }
问题分析
初始Layout的问题:App目录的RootLayout必须包含
<html>和<head>标签,你提供的初始Layout直接返回<body>,不符合Next.js规范,大概率无法被正确识别;同时dataLoaded是自定义props,但App目录的Layout不会自动传入该参数,导致useEffect无法触发;另外即使loading为true,children会直接渲染在Loading组件之后,若children有背景色会覆盖Loading。Suspense方案的问题:React Suspense仅在组件触发Suspense边界时才会显示fallback内容,即当子组件存在异步操作(比如Server Component中的
await数据请求、客户端组件的lazy动态导入)时才会生效。如果你的页面/组件都是同步渲染,Suspense不会触发,自然不会显示Loading。
解决方案
方案1:使用App目录内置的loading.js(推荐)
Next.js App目录提供了原生加载页支持,无需手动管理状态或Suspense:
- 在
app目录下创建loading.jsx文件,直接导出加载组件:
// app/loading.jsx export default function Loading() { return ( <div className="loading w-screen h-screen bg-red-100 fixed top-0 left-0 z-50 flex items-center justify-center"> <p>Loading...</p> </div> ); }
Next.js会自动在页面路由切换、数据加载过程中显示该加载页,加载完成后自动隐藏。
方案2:正确使用Suspense
如果需要手动控制Suspense,必须确保子组件存在异步操作:
- 在页面组件中添加异步数据请求(以Server Component为例):
// app/page.jsx async function fetchData() { // 模拟异步数据请求 await new Promise(resolve => setTimeout(resolve, 2000)); return { content: "页面内容" }; } export default async function Home() { const data = await fetchData(); return <div>{data.content}</div>; }
- 此时RootLayout中的Suspense会触发,显示fallback的Loading组件。
方案3:修复初始的useState+useEffect方案
- 确保Layout是符合规范的RootLayout(包含
<html>和<head>); - 手动控制加载状态,给Loading组件添加固定定位避免被覆盖:
// app/layout.jsx import "./globals.css"; import React, { useState, useEffect } from "react"; import Loading from "@/components/loading/loading"; export default function RootLayout({ children }) { const [loading, setLoading] = useState(true); useEffect(() => { // 页面加载完成后隐藏加载页 const handleLoad = () => setLoading(false); window.addEventListener("load", handleLoad); return () => window.removeEventListener("load", handleLoad); }, []); return ( <html lang="en"> <head /> <body> {loading && ( <div className="fixed top-0 left-0 w-screen h-screen bg-red-100 z-50 flex items-center justify-center"> <p>Loading...</p> </div> )} {children} </body> </html> ); }
内容的提问来源于stack exchange,提问作者Omar
相关产品推荐
相关产品推荐

