MST

星途 面试题库

面试题:Next.js 自定义 Document 组件与 SSR/SSG 性能优化的深度结合

在大型 Next.js 项目中,自定义 Document 组件在服务器端渲染(SSR)和静态站点生成(SSG)场景下,怎样与性能优化策略深度结合。详细说明在组件层面如何优化首屏加载速度、减少重复渲染,例如对样式加载、脚本注入的优化策略,同时分析不同优化策略对 SSR 和 SSG 的影响。
10.6万 热度难度
前端开发Next.js

知识考点

AI 面试

面试题答案

一键面试

1. 组件层面优化首屏加载速度

样式加载优化

  • SSR 场景
    • 内联关键 CSS:在 Document 组件的 getInitialProps 方法中(用于 SSR),提取首屏渲染所需的关键 CSS 并内联到 HTML 中。这样浏览器在解析 HTML 时就能立即应用样式,减少首次渲染的白屏时间。例如,可以使用 styled - componentsServerStyleSheet 来提取样式:
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheet } from'styled - components';

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const sheet = new ServerStyleSheet();
    const originalRenderPage = ctx.renderPage;

    try {
      ctx.renderPage = () =>
        originalRenderPage({
          enhanceApp: (App) => (props) => sheet.collectStyles(<App {...props} />)
        });

      const initialProps = await Document.getInitialProps(ctx);
      return {
      ...initialProps,
        styles: (
          <>
          {initialProps.styles}
          {sheet.getStyleElement()}
          </>
        )
      };
    } finally {
      sheet.seal();
    }
  }

  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}
  • 字体加载优化:在 Head 标签内使用 @font - face 规则,通过设置 font - display 属性来控制字体加载行为。例如,设置 font - display: swap,在字体加载时先使用系统字体渲染文本,字体加载完成后再切换,避免文本闪烁。
<Head>
  <style>
    @font - face {
      font - family: 'MyFont';
      src: url('/fonts/myfont.woff2') format('woff2'),
        url('/fonts/myfont.woff') format('woff');
      font - display: swap;
    }
  </style>
</Head>
  • SSG 场景
    • 预生成 CSS:在构建阶段(使用 next build 进行 SSG),生成所有页面的 CSS 文件,并通过 next.config.js 配置文件中的 cssMinimizer 进行压缩。这样在页面加载时,浏览器可以快速下载并解析 CSS。
    • 代码拆分 CSS:使用工具如 mini - css - extract - plugin 将 CSS 从 JavaScript 中拆分出来,实现并行加载。在 Next.js 项目中,next - css 插件默认会将 CSS 提取到单独文件。这有助于提高首屏加载速度,因为浏览器可以同时请求 HTML、CSS 和 JavaScript 文件。

脚本注入优化

  • SSR 场景
    • 异步加载非关键脚本:对于那些不影响首屏渲染的脚本,如第三方统计脚本或广告脚本,在 Document 组件的 render 方法中使用 script 标签,并设置 async 属性。这样脚本会在页面解析完成后异步加载,不会阻塞首屏渲染。
render() {
  return (
    <Html>
      <Head />
      <body>
        <Main />
        <NextScript />
        <script async src="https://example.com/analytics.js"></script>
      </body>
    </Html>
  );
}
  • 内联关键脚本:对于那些对首屏交互至关重要的脚本,如初始化页面交互的脚本,可以内联到 HTML 中。但要注意内联脚本不能过大,以免影响 HTML 的解析速度。
  • SSG 场景
    • 动态导入脚本:对于某些脚本,只有在特定页面或用户交互时才需要,可以使用动态导入。例如,在 Next.js 中,可以使用 next/dynamic 动态导入组件,同时该组件依赖的脚本也会按需加载。这有助于减少初始页面加载的脚本体积。
import dynamic from 'next/dynamic';

const MyComponent = dynamic(() => import('../components/MyComponent'), {
  ssr: false
});

2. 减少重复渲染

  • SSR 场景
    • 缓存数据:在 getInitialProps 方法中,可以使用缓存机制来避免重复获取相同的数据。例如,可以使用 Node.js 的 lru - cache 库来缓存 API 响应数据。如果相同的数据请求再次发生,直接从缓存中获取,减少数据库或 API 的调用次数,从而减少重复渲染。
import LRU from 'lru - cache';

const cache = new LRU({
  max: 100,
  maxAge: 1000 * 60 * 5
});

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const key = JSON.stringify(ctx.query);
    let data = cache.get(key);
    if (!data) {
      data = await fetchData(ctx.query);
      cache.set(key, data);
    }
    return { data };
  }

  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}
  • Memoization:对于 Document 组件中的纯函数,如样式生成函数,可以使用 React.memo 或手动实现 memoization 来避免不必要的重新计算。这在组件属性没有变化时,防止重复渲染。
  • SSG 场景
    • Incremental Static Regeneration:Next.js 支持增量静态再生。通过设置 revalidate 选项,可以在页面构建后定期重新生成静态页面。这有助于减少重复渲染,因为只有在数据发生变化时才会重新生成页面,而不是每次请求都重新渲染。例如,在页面组件中设置:
export async function getStaticProps({ params }) {
  const data = await fetchData(params.id);
  return {
    props: { data },
    revalidate: 60 * 60 * 24 // 一天后重新生成
  };
}
  • Static Props Caching:在构建阶段,Next.js 会缓存 getStaticProps 的结果。如果页面依赖的数据没有变化,后续构建不会重新执行 getStaticProps,从而减少重复渲染。

3. 不同优化策略对 SSR 和 SSG 的影响

  • 样式加载优化
    • SSR:内联关键 CSS 可以显著提高首屏加载速度,因为服务器端直接将样式注入 HTML,浏览器无需额外请求。但如果内联样式过多,会增加 HTML 的大小,影响解析速度。字体加载优化策略对 SSR 和 SSG 同样重要,都能提升用户体验。
    • SSG:预生成和压缩 CSS 文件在 SSG 场景下效果很好,因为构建时生成的静态文件可以直接被浏览器快速加载。代码拆分 CSS 能充分利用浏览器的并行加载能力,进一步提高加载速度。但如果 CSS 文件过多或过大,可能会增加请求数量和总下载大小。
  • 脚本注入优化
    • SSR:异步加载非关键脚本可以避免阻塞首屏渲染,但可能会导致脚本执行延迟,影响后续交互。内联关键脚本虽然能提高首屏交互速度,但过大的内联脚本会增加 HTML 解析时间。
    • SSG:动态导入脚本在 SSG 中非常有效,因为它可以按需加载脚本,减少初始加载体积。但如果动态导入过于频繁,可能会增加请求次数,影响性能。
  • 减少重复渲染
    • SSR:缓存数据和 memoization 可以有效减少重复渲染,提高服务器端渲染效率。但缓存管理需要注意数据的时效性,避免使用过期数据。
    • SSG:增量静态再生和静态 props 缓存是 SSG 减少重复渲染的重要手段。增量静态再生确保页面数据的实时性,而静态 props 缓存则提高构建效率。但增量静态再生可能会增加服务器负载,需要合理设置 revalidate 时间。