Skip to content

Next.js 静态化 (Static Site Generation)

1. 渲染模式演进与静态化的兴起

在之前的学习中,我们了解了两种主流的渲染模式及其优缺点:

  1. CSR (客户端渲染):

    • 优点: 服务器负载极低,仅提供静态文件和 API 代理。
    • 缺点: 首屏加载慢(白屏时间长),对 SEO 极不友好。
  2. SSR (服务端渲染):

    • 优点: 解决了白屏和 SEO 问题,用户体验好。
    • 缺点: 服务器负载高。每个用户的每次请求都需要在服务器上实时渲染一次页面,消耗大量 CPU 资源。

为了结合两者的优点,Next.js 9+ 版本大力推行了一种更优的模式:静态化,也称为 SSG (Static Site Generation)

2. 核心理念:构建时预渲染 (Pre-rendering at Build Time)

静态化的核心思想: 与其在每次请求时渲染页面 (SSR),不如在项目构建打包时就提前将页面渲染成静态的 HTML 文件。

工作流程对比:

  • SSR: 请求 -> 服务器实时渲染组件 -> 返回 HTML
  • SSG: 构建打包 -> 提前渲染所有静态页面为 HTML 文件请求 -> 服务器直接返回对应的静态 HTML 文件

这种模式极大地降低了服务器的压力,响应速度接近于直接提供静态文件,同时保留了 SSR 的所有优点(快速首屏、SEO 友好)。

3. Next.js 的预渲染策略

Next.js 将所有在服务端生成 HTML 的过程统称为预渲染 (Pre-rendering),并提供了两种模式:

  1. SSG (Static Site Generation): 在构建时生成 HTML。这是 Next.js 默认和推荐的模式。
  2. SSR (Server-Side Rendering): 在每次请求时生成 HTML。

自动静态优化 (Automatic Static Optimization)

Next.js 非常智能,它会默认尝试将你的每一个页面都进行静态化 (SSG)。只有当它检测到某个页面需要依赖请求特定数据时,它才会自动降级为 SSR 模式。

对于所有不依赖请求数据的页面(例如首页、关于我们页),Next.js 都会自动将其优化为纯静态页面。

4. 验证纯静态化

4.1. 实验设置

在页面组件中加入 console.log 来观察其渲染时机。

jsx
// pages/index.jsx
const HomePage = () => {
    console.log("Index render"); // 添加日志
    return <h1>首页</h1>;
};
export default HomePage;
jsx
// pages/movies/index.jsx
const MoviesPage = () => {
    console.log("Movies render"); // 添加日志
    return <h1>电影页</h1>;
};
export default MoviesPage;

4.2. 观察不同环境下的行为

  • 开发环境 (npm run dev): 刷新页面,服务器终端每次都会打印出 "Index render"。 原因: 在开发模式下,为了获得最佳的调试体验和代码热更新,Next.js 会为每次请求都重新渲染页面,静态化功能被暂时禁用。

  • 生产环境 (npm run build & npm start):

    1. 构建: 运行 npm run build

      • 关键现象: 在构建过程中,终端会打印出 "Index render" 和 "Movies render"。这证明了页面是在打包时被渲染的,此时服务器还未启动,没有任何用户请求。
      • 构建输出:
        Page            Size     First Load JS
        ┌ ○ /           2.5 kB         75.1 kB
        ├   /_app       0 B            72.6 kB
        ├ ○ /404        194 B          72.8 kB
        └ ○ /movies     2.53 kB        75.1 kB
        
        ○ (Static)   automatically rendered as static HTML
        日志中的 ○ (Static) 符号明确表示这些页面已被自动优化为静态 HTML。
    2. 启动: 运行 npm start 启动生产服务器。

      • 关键现象: 此时刷新浏览器访问首页或电影页,服务器终端不再打印任何渲染日志
      • 原因: 服务器现在只是一个静态文件服务员,它直接将构建时生成的 .next/server/pages/index.html 等文件返回给浏览器,完全没有执行组件的渲染逻辑。

5. 静态页面中的动态内容(混合渲染模式)

思考: 如果一个页面的大部分内容是静态的,但有一小部分是用户特定的动态内容(如广告、评论、登录状态),是否就必须放弃静态化,退回到 SSR 模式?

答案是不需要! 这正是静态化强大的地方,我们可以采用混合渲染的策略:

  • 核心内容 (对 SEO 和首屏速度至关重要): 使用 Next.js 进行静态化预渲染。例如一篇文章的主体内容。
  • 非核心动态内容 (对 SEO 不重要或用户特定): 在页面骨架加载后,通过客户端 JS 来获取和渲染。例如文章下的评论列表、个性化广告。

示例:在静态页面上加载广告

jsx
// pages/index.jsx
import { useState, useEffect } from 'react';

const HomePage = () => {
    // 1. 广告状态初始为空数组
    const [ads, setAds] = useState([]);

    // 2. 在客户端加载广告数据
    useEffect(() => {
        // 这里的代码只会在浏览器端运行
        // 假设这里有一个 fetchAds() 的 API 请求
        const fetchedAds = [
            { name: '广告1', link: '...' },
            { name: '广告2', link: '...' }
        ];
        setAds(fetchedAds);
    }, []); // 空依赖数组,只在组件首次挂载时运行一次

    return (
        <div>
            <h1>首页</h1>
            {/* ...其他静态内容... */}
            <hr />
            <h2>广告位</h2>
            <ul>
                {ads.map(ad => (
                    <li key={ad.name}>
                        <a href={ad.link}>{ad.name}</a>
                    </li>
                ))}
            </ul>
        </div>
    );
};

export default HomePage;

渲染流程:

  1. 构建时: 服务器执行 HomePage 组件,ads 状态是 []useEffect 不会执行。最终生成的静态 HTML 中,广告位的 <ul> 是空的。
  2. 请求时: 浏览器收到这个不含广告的静态 HTML 并迅速渲染。
  3. 客户端激活: 页面上的 JS 开始执行,React 接管页面。useEffect 被触发,发起请求获取广告数据,并通过 setAds 更新状态,最终将广告渲染到页面上。

这种模式完美结合了 SSG 的极致性能和 CSR 的动态灵活性。