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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 22:55:00