2025年07月24日7 分钟

SSG 静态站点生成:原理、流程与最佳实践

深入理解 SSG(Static Site Generation)的工作原理,为什么它是内容型网站的最佳选择

什么是 SSG

SSG(Static Site Generation,静态站点生成)是指在构建时(build time) 就把所有页面预渲染成完整的 HTML 文件。部署后,服务器只需要返回这些已经生成好的 HTML,不需要任何运行时计算。

CSR:  用户请求 → 服务器返回空 HTML → 浏览器 JS 渲染
SSR:  用户请求 → 服务器实时渲染 HTML → 返回给浏览器
SSG:  构建时生成 HTML → 用户请求 → CDN 直接返回 HTML(最快)

SSG 的完整流程

                    构建阶段(npm run build)
 ┌──────────────────────────────────────────────────┐
 │                                                   │
 │  1. 扫描所有页面路由                                │
 │  2. 对每个路由:                                    │
 │     ├── 调用数据获取函数(getStaticProps)           │
 │     ├── 获取 API/CMS/文件系统的数据                 │
 │     ├── 执行 React 渲染 → 生成 HTML 字符串          │
 │     └── 写入 .html 文件                            │
 │  3. 同时生成对应的 JSON 数据文件                     │
 │                                                   │
 │  输出:                                            │
 │  /out                                             │
 │  ├── index.html          ← 首页                    │
 │  ├── about.html          ← 关于页                  │
 │  ├── blog/                                        │
 │  │   ├── post-1.html     ← 博客文章1               │
 │  │   ├── post-2.html     ← 博客文章2               │
 │  │   └── post-3.html     ← 博客文章3               │
 │  └── _next/data/                                  │
 │      └── posts.json      ← 客户端路由用的数据        │
 └──────────────────────────────────────────────────┘

                    运行阶段(用户访问)
 ┌──────────────────────────────────────────────────┐
 │                                                   │
 │  用户请求 /blog/post-1                             │
 │       │                                           │
 │       ▼                                           │
 │  CDN 直接返回 post-1.html ← 不需要服务器计算        │
 │       │                                           │
 │       ▼                                           │
 │  浏览器立即渲染完整内容 ← 毫秒级响应                  │
 │       │                                           │
 │       ▼                                           │
 │  JS 加载后 Hydration → 页面可交互                   │
 │                                                   │
 └──────────────────────────────────────────────────┘

Next.js 中的 SSG 实现

App Router(推荐)

// app/blog/[slug]/page.tsx

// 构建时告诉 Next.js 有哪些路径需要预渲染
export async function generateStaticParams() {
  const posts = await getAllPosts()
  return posts.map(post => ({
    slug: post.slug,  // 生成 /blog/hello-world, /blog/react-tips 等
  }))
}

// 构建时为每个 slug 执行一次,获取数据
async function BlogPost({ params }: { params: { slug: string } }) {
  const post = await getPostBySlug(params.slug)

  return (
    <article>
      <h1>{post.title}</h1>
      <time>{post.date}</time>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  )
}

export default BlogPost

Pages Router(旧版)

// pages/blog/[slug].tsx

// 构建时获取所有路径
export async function getStaticPaths() {
  const posts = await getAllPosts()
  return {
    paths: posts.map(post => ({ params: { slug: post.slug } })),
    fallback: false,  // 未预渲染的路径返回 404
  }
}

// 构建时为每个路径获取数据
export async function getStaticProps({ params }) {
  const post = await getPostBySlug(params.slug)
  return {
    props: { post },
  }
}

function BlogPost({ post }) {
  return <article>{post.title}</article>
}

SSG 的核心优势

1. 极致的加载速度

SSG 的响应路径:
  用户 → CDN 边缘节点 → 返回 HTML(缓存命中)
  延迟: 5-50ms(取决于物理距离)

SSR 的响应路径:
  用户 → CDN → 源服务器 → 数据库 → 渲染 → 返回
  延迟: 200-2000ms

差距: 10-100 倍

2. 天然 CDN 友好

           ┌── 北京节点(缓存 HTML)
           │
用户请求 ──┼── 上海节点(缓存 HTML)
           │
           └── 广州节点(缓存 HTML)

每个 CDN 节点都缓存了完整的 HTML
用户从最近的节点获取,不需要回源

3. 完美的 SEO

<!-- 爬虫看到的 SSG 页面:完整内容 -->
<article>
  <h1>React 性能优化技巧</h1>
  <p>性能直接影响用户体验...</p>
  <pre><code>const MemoComp = React.memo(...</code></pre>
</article>
<!-- 所有内容都在 HTML 中,爬虫完美抓取 -->

4. 零服务器负担

SSR:  100 用户同时访问 → 服务器执行 100 次渲染 → 高 CPU 负载
SSG:  100 用户同时访问 → CDN 返回 100 份缓存 → 服务器零负载

SSG 的"服务器"可以是:
  - GitHub Pages(免费)
  - Vercel(免费额度很大)
  - 任何静态文件托管(S3、OSS)
  - 不需要 Node.js 运行时

SSG 的局限性

1. 构建时间与页面数量

100 个页面  → 构建 30 秒    ✅
1000 个页面 → 构建 5 分钟    😐
10000 个页面 → 构建 30+ 分钟  😱

解决方案:ISR(增量静态再生)

2. 不适合实时数据

场景:电商网站的商品价格

SSG 构建时:价格 = ¥99
用户访问时:价格已经改为 ¥79
但 HTML 仍然显示 ¥99 ← 过期数据!

必须重新构建才能更新价格

3. 不适合个性化内容

SSG 生成的页面对所有用户都一样:
  "欢迎,用户!"  ← 不知道是谁

不能做到:
  "欢迎,Alice!您有 3 条新消息" ← 个性化内容需要 SSR 或 CSR

适用场景

✅ 完美适合 SSG:
  ├── 博客 / 技术文档
  ├── 产品官网 / 落地页
  ├── 帮助中心 / FAQ
  ├── 作品集展示
  ├── 内容很少变动的页面
  └── 本博客就是用 SSG 构建的!

❌ 不适合 SSG:
  ├── 实时数据展示(股票、天气)
  ├── 个性化内容(推荐、Feed 流)
  ├── 频繁更新的大量页面
  └── 用户登录后的个人页面

SSG + CSR 混合模式

实际项目中,SSG 常常和 CSR 结合使用:

// 博客详情页:静态内容用 SSG,评论用 CSR
export async function generateStaticParams() {
  return posts.map(p => ({ slug: p.slug }))
}

async function BlogPost({ params }) {
  const post = await getPost(params.slug)

  return (
    <article>
      {/* SSG 部分:构建时生成 */}
      <h1>{post.title}</h1>
      <div>{post.content}</div>

      {/* CSR 部分:客户端动态加载 */}
      <CommentSection postId={post.id} />
    </article>
  )
}

// 评论组件:客户端渲染
'use client'
function CommentSection({ postId }) {
  const [comments, setComments] = useState([])

  useEffect(() => {
    fetch(`/api/comments?postId=${postId}`)
      .then(res => res.json())
      .then(setComments)
  }, [postId])

  return <div>{comments.map(c => <Comment key={c.id} {...c} />)}</div>
}

总结

SSG 是内容型网站的最佳渲染模式:加载最快、SEO 最好、成本最低。对于数据更新频率较高的场景,可以搭配 ISR(增量静态再生)来获得两全其美的效果。