Next.js中getServerSideProps加载时如何设置骨架屏?路由切换无延迟方案
在Next.js中实现getServerSideProps加载状态与无延迟路由切换骨架屏
一、先解决你当前代码的问题
你现在的写法有个关键问题:getServerSideProps是在服务器端同步执行的,只有等请求完全结束后,才会把数据作为props传给页面组件。所以fetch里的loading状态根本传不到客户端——页面在服务器渲染完成前,客户端要么看到Next.js默认的加载态,要么是空白,永远不会触发你写的<Skeleton/>。
要在组件里显示骨架屏,得换个思路,下面是两种可行的修复方式:
方案1:客户端发起请求+本地状态管理
把数据请求从getServerSideProps移到组件内部,用useState和useEffect管控加载状态:
import { useState, useEffect } from 'react'; import Skeleton from './Skeleton'; import Content from './Content'; export default function Index() { const [loading, setLoading] = useState(true); const [business, setBusiness] = useState(null); const [errorCode, setErrorCode] = useState(null); useEffect(() => { const fetchData = async () => { try { const res = await fetch(url); const { errorCode, business } = await res.json(); setBusiness(business); setErrorCode(errorCode); } catch (err) { setErrorCode('FETCH_ERROR'); } finally { setLoading(false); } }; fetchData(); }, []); if (loading) { return <Skeleton />; } return <Content data={business} />; }
缺点是会失去SSR的SEO优势,适合不需要搜索引擎收录的页面。
方案2:保留SSR+路由切换骨架屏
如果要保留服务端渲染,同时在路由切换时显示骨架屏,可以用useRouter监听路由变化:
import { useRouter } from 'next/router'; import { useState, useEffect } from 'react'; import Skeleton from './Skeleton'; import Content from './Content'; export const getServerSideProps = async (context) => { const res = await fetch(url); const { errorCode, business } = await res.json(); return { props: { errorCode: errorCode || null, business: business || null, }, }; }; export default function Index(props) { const router = useRouter(); const [isRouteChanging, setIsRouteChanging] = useState(false); useEffect(() => { const handleStart = () => setIsRouteChanging(true); const handleComplete = () => setIsRouteChanging(false); router.events.on('routeChangeStart', handleStart); router.events.on('routeChangeComplete', handleComplete); router.events.on('routeChangeError', handleComplete); return () => { router.events.off('routeChangeStart', handleStart); router.events.off('routeChangeComplete', handleComplete); router.events.off('routeChangeError', handleComplete); }; }, [router]); // 路由切换时立刻显示骨架屏,首屏直接用SSR返回的内容 if (isRouteChanging) { return <Skeleton />; } return <Content data={props.business} />; }
二、路由切换无延迟显示骨架屏的其他方案
1. 页面专属骨架屏预加载
给每个页面写对应的骨架屏组件,路由切换时根据路径渲染:
- 给每个页面创建
XXXSkeleton.tsx(比如HomeSkeleton.tsx、PostSkeleton.tsx) - 在_app.js里监听路由变化,匹配对应骨架屏:
import { useRouter } from 'next/router'; import { useState, useEffect } from 'react'; import HomeSkeleton from './HomeSkeleton'; import PostSkeleton from './PostSkeleton'; import Skeleton from './Skeleton'; export default function App({ Component, pageProps }) { const router = useRouter(); const [isLoading, setIsLoading] = useState(false); useEffect(() => { const handleStart = () => setIsLoading(true); const handleComplete = () => setIsLoading(false); router.events.on('routeChangeStart', handleStart); router.events.on('routeChangeComplete', handleComplete); router.events.on('routeChangeError', handleComplete); return () => { router.events.off('routeChangeStart', handleStart); router.events.off('routeChangeComplete', handleComplete); router.events.off('routeChangeError', handleComplete); }; }, [router]); if (isLoading) { switch (router.pathname) { case '/': return <HomeSkeleton />; case '/posts/[id]': return <PostSkeleton />; default: return <Skeleton />; } } return <Component {...pageProps} />; }
这种方式的骨架屏和目标页面布局完全匹配,用户几乎感知不到延迟。
2. 用SWR/React Query自带加载态
这类数据请求库会自动管理加载状态,还能缓存数据,路由切换回来时直接用缓存:
以SWR为例:
import useSWR from 'swr'; import Skeleton from './Skeleton'; import Content from './Content'; const fetcher = async (url) => { const res = await fetch(url); return res.json(); }; export default function Index() { const { data, error, isLoading } = useSWR(url, fetcher); if (isLoading) return <Skeleton />; if (error) return <div>加载失败</div>; return <Content data={data.business} />; }
3. Next.js 13+ App Router 原生方案
如果用新版App Router,直接在页面目录下创建loading.js,路由切换时会自动显示这个组件:
// app/home/loading.js export default function Loading() { return <Skeleton />; }
服务器渲染完成前,客户端会立刻显示骨架屏,完全无延迟,这是官方推荐的方案。
内容的提问来源于stack exchange,提问作者rajiv dahal
相关产品推荐
相关产品推荐

