Nextjs页面加载速度优化

188 阅读6分钟

boombb12138.notion.site/1662d40e2ab…

首页

before

image.png

after

image.png

具体是怎么做的?

使用了ISR增量渲染

export async function getStaticProps() {
  const data = await fetchData();

  return {
    props: {
      data,
    },
    // 添加 revalidate 使页面在生产环境中也能更新
    revalidate: 60, // 每60秒重新生成页面
  }
}

通过getStaticProps一次性拿到所有类型的tab数据 再传递给homeSwiper和TabSwiper; 切换 tab 时无需等待新的请求

// 在开发环境中 (npm run dev):
- getStaticProps 在每次请求时运行
- 可以看到数据实时更新
- 控制台会打印日志

// 在生产环境中 (npm run build && npm run start):
- getStaticProps 是在构建时执行的,而不是每个请求时

一开始我以为如果在网络请求的时候使用了process.env.ENDPOINT来配置节点,在服务端运行的getStaticProps可能拿不到,所以使用 (process.env.ENDPOINT|| 写死的节点)了

这是一个workaround,无论是普通环境变量还是带 NEXT_PUBLIC_ 前缀的变量都可以在服务端使用,具体原因还没有找到(逃

环境变量和环境变量文件

  1. 服务端环境变量

    • 普通的环境变量(不带 NEXT_PUBLIC_ 前缀)只能在服务端使用
    • 这些变量可以在 getStaticProps 中正常访问
    // .env.local
    API_SECRET=123456
    
    // getStaticProps 中可以访问
    console.log(process.env.API_SECRET) // 输出: 123456
    
  2. 公共环境变量

    • 以 NEXT_PUBLIC_ 开头的环境变量在客户端和服务端都可以访问
    // .env.local
    NEXT_PUBLIC_API_URL=https://api.example.com
    
    // getStaticProps 中也可以访问
    console.log(process.env.NEXT_PUBLIC_API_URL)
    
  3. 环境变量文件优先级

    .env.$(NODE_ENV).local
    .env.local
    .env.$(NODE_ENV)
    .env
    

这样子修改后,build结果是


Route (pages)                              Size     First Load JS
┌ ● / (ISR: 60 Seconds) (864 ms)           378 B           743 kB
├   /_app                                  0 B             738 kB
├ ○ /404                                   186 B           738 kB
├ λ /api/hello                             0 B             738 kB
├ ○ /Building                              619 B           739 kB
├ ○ /CreateIdea                            2 kB            817 kB
。。。
├ ○ /Guide                                 653 B           739 kB
├ λ /MarketDetail/[questionId]             202 B           738 kB
├ ○ /MarketGroup                           192 B           738 kB
├ ○ /UserGuidance                          668 B           739 kB
├ ● /Vote (853 ms)                         791 B           739 kB
+ First Load JS shared by all              791 kB
  ├ chunks/framework-ca706bf673a13738.js   45.3 kB
  ├ chunks/main-049f9ab47cf6faf9.js        32.9 kB
  ├ chunks/pages/_app-ad9b86ab2ac1370a.js  658 kB
  ├ chunks/webpack-be009f8a37bb749e.js     2.05 kB
  └ css/493a2937cdeb4605.css               53.1 kB

λ  (Server)  server-side renders at runtime (uses getInitialProps or getServerSideProps)
○  (Static)  automatically rendered as static HTML (uses no initial props)
●  (SSG)     automatically generated as static HTML + JSON (uses getStaticProps)
   (ISR)     incremental static regeneration (uses revalidate in getStaticProps)

优化的时候,以lighthouse的数据的数据为准,因为它最能反应用户体验

filter页面

image.png

before

image.png

after

使用了SSR

使用了getServerSideProps,获取page=1的数据,首次挂载的时候,useEffect set page=1的数据,用户交互选择了筛选条件就再getGroups里面发起网络请求。

在测试的时候,要使用npm run build,start来检测性能,同时运行nom run dev,改变端口:

{
  "scripts": {
    "dev": "next dev -p 3001"
  }
}

减少Cumulative Layout Shift(表示页面元素在加载过程中发生了较大的位移,这会影响用户体验)

  • 设置骨架屏
  • Image设置宽高

Market Group

image.png

before

image.png

after

同样使用getServerSideProps,

export async function getServerSideProps({ query }) {
  try {
    const groupId = query?.groupId;//获取url中的参数

    if (!groupId) {
      return {
        notFound: true,
      };
    }

    // 1. Parallel data fetching
    const [groupDetailRes, groupBriefRes] = await Promise.all([
      apiTestnetGetGroupDetail(groupId),
      apiMainnetGetGroupBrief(groupId),
    ]);

    // 2. Calculate derived data server-side
    const listData = groupBriefRes.data.data;
    const sumVolume = listData.reduce(
      (sum, obj) => sum + (typeof obj.volume === "number" ? obj.volume : 0),
      0
    );
    const sumLiquidity = listData.reduce(
      (sum, obj) =>
        sum + (typeof obj.liquidity === "number" ? obj.liquidity : 0),
      0
    );

    return {
      props: {
        groupBrief: groupDetailRes.data.data,
        initialListData: listData,
        initialSumVolume: sumVolume,
        initialSumLiquidity: sumLiquidity,//提前获取所有需要从接口拿到的数据
      },
    };
  } catch (error) {
    return { notFound: true };
  }
}

如果拿到时间后,在展示的时候,直接.toLocaleString会出现水合的错误,如下

Error: Text content does not match server-rendered HTML.

Warning: Text content did not match. Server: "7/1/2024, 12:00:00 AM" Client: "2024/7/1 00:00:00"

See more info here: <https://nextjs.org/docs/messages/react-hydration-error> 具体是哪里代码出错

因为服务端展示的是标准时间,客户端展示的时候本地时间,所以为了显示本地时间,同时避免服务端和客户端渲染不一致的问题,时间的格式化在useEffect里面处理

const TimeDisplay = ({ timestamp }) => {
  const [formattedTime, setFormattedTime] = useState("");

  useEffect(() => {
    const date = new Date(timestamp * 1000);
    setFormattedTime(date.toLocaleString());
  }, [timestamp]);

  // 初始渲染时返回一个占位符或空字符串
  if (!formattedTime) return <span>Loading...</span>;
  return <span>{formattedTime}</span>;
};
<TimeDisplay timestamp={groupBrief?.close_time} />

MarketDetail

bug record:

使用npm run start而不是run dev因为build之后体积会减小

使用无痕模式 Chrome DevTools 中的 Lighthouse 标签来看性能

  • 浏览器学习资料

    以下是一些学习 Chrome DevTools 的优质资源:

    • 官方资源

      • Chrome DevTools 官方文档
      • 最权威和完整的参考资料
      • 提供互动式教程
      • 有中文版本
    • 重点学习模块

    • Elements(元素)

      • DOM树检查和修改
      • CSS样式调试
      • 元素状态查看
    • Console(控制台)

      • 日志输出
      • JavaScript调试
      • 错误监控
    • Network(网络)

      • 网络请求监控
      • 加载时间分析
      • 资源大小查看
    • Performance(性能)

      • CPU使用率
      • 内存占用
      • 页面加载分析

    推荐的学习路径

    初级阶段:

    • Elements 和 Console 面板基础操作
    • 网络请求监控(Network面板)
    • 移动设备模拟器使用

    中级阶段:

    • JavaScript调试技巧
    • 性能分析基础
    • 内存泄漏检测

    高级阶段:

    • 性能优化技巧
    • 安全审计
    • 高级调试技巧
    • 实用在线教程
    • Chrome DevTools 中文手册
    • 掘金 Chrome DevTools 专栏
    • Google Web.dev
    • YouTube 频道推荐
    • Google Chrome Developers
    • DevTools Tips
    • Umar Hansa
    • 实践建议
    • 从简单的项目开始练习
    • 多使用快捷键提高效率
    • 尝试分析知名网站的性能
    • 参与开源项目调试
  • chrome中的performance和lighthouse里面的performance一样吗?不一样

    不一样,它们是两个不同的性能分析工具,有不同的用途:

    1. Chrome DevTools 的 Performance 面板:

    • 提供详细的运行时性能分析

    • 记录和分析页面的实时性能表现

    • 显示具体的:

      • JavaScript 执行时间
      • 布局重排(Layout)
      • 重绘(Paint)
      • 帧率(FPS)
      • CPU使用情况
      • 网络请求时间线
    • 适合诊断具体的性能瓶颈和问题

    1. Lighthouse 的 Performance:

    • 提供整体的性能评分和建议

    • 基于一系列标准化的性能指标:

      • First Contentful Paint (FCP)
      • Largest Contentful Paint (LCP)
      • Time to Interactive (TTI)
      • Total Blocking Time (TBT)
      • Cumulative Layout Shift (CLS)
    • 给出具体的优化建议

    • 生成可分享的性能报告

    • 更适合整体性能评估和优化方向指导

    总结:

    • Performance 面板适合深入分析具体的性能问题
    • Lighthouse 适合获取整体性能评分和优化建议
    • 两者结合使用能够更全面地进行性能优化

    建议:

    1. 先用 Lighthouse 获取整体性能评分和优化方向
    2. 再用 Performance 面板深入分析具体问题
    3. 根据分析结果有针对性地进行优化

图片优化

Use next/image instead of regular tags;

使用priority让next优先渲染这个图片,适合明显的大图

设置宽高

<Image
    src={image_url}
    alt={group_name || "Market image"}
    width={200}
    height={200}//先这样子设置
    priority={props.isPriority}
    quality={75} 
    style={{
      objectFit: "cover",//再配置cover
      backgroundColor: "#f2f2f2",
    }}
  />

减少Cumulative Layout Shift(表示页面元素在加载过程中发生了较大的位移,这会影响用户体验)

  • 设置骨架屏
  • Image设置宽高
  • 使用SSR等渲染方案