Next.js SSG 与同类方案怎么选:实测对比与选型指南

主题: nextjs-static-site-generation更新于: 2026/7/2作者:AgentFactory 技术团队

静态站点生成(SSG)是前端工程化中一个成熟但常被误用的技术。Next.js 的 SSG 能力并非孤立存在,它与 Gatsby、Hugo、Jekyll 等方案在构建速度、动态内容支持、部署复杂度上存在显著差异。本文从实战角度出发,通过多维对比和具体配置示例,帮你快速判断在什么场景下该选谁,以及如何避免踩坑。

它解决什么问题 / 适用场景

SSG 的核心价值在于:在构建时预渲染页面为静态 HTML,部署后无需服务端处理即可直接响应请求。这带来了极致的首屏加载速度和 CDN 缓存效率。

最适合的场景

  • 内容不频繁变化的网站:营销页面、博客文章、作品集、电商产品列表、帮助文档
  • 与无头 CMS(如 Contentful、Sanity、Strapi)配合使用,在构建时获取数据并生成静态 HTML
  • 需要高 SEO 评分且对动态内容要求低的页面

不适合的场景

  • 需要频繁更新数据的页面(如实时仪表盘、社交信息流)
  • 用户个性化内容(如登录后的个人主页)
  • 需要实时交互的复杂应用(如在线编辑器)

在这些场景下,应考虑客户端数据获取(useEffect + fetch)或服务端渲染(SSR)。

安装与快速上手

使用 Next.js 创建 SSG 项目非常简单。以下命令会创建一个带 TypeScript、ESLint、Tailwind CSS、src/ 目录和 App Router 的样板项目:

BASH
npx create-next-app@latest my-static-site --typescript --eslint --src-dir --app-router

在项目根目录下,创建 src/app/page.tsx 文件,写入以下代码即可实现一个最简单的 SSG 页面:

TSX
// src/app/page.tsx
export default async function HomePage() {
  // 在构建时获取数据
  const res = await fetch('https://jsonplaceholder.typicode.com/posts');
  const posts = await res.json();

  return (
    <div>
      <h1>Static Posts</h1>
      <ul>
        {posts.slice(0, 10).map((post: { id: number; title: string }) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

运行 npm run build 后,所有页面将被预渲染为静态 HTML 文件,输出到 .next 目录。

核心配置 / 参数说明

Next.js SSG 的核心在于 getStaticPropsgetStaticPaths 两个函数(Pages Router)或 App Router 下的 generateStaticParams。以下是关键参数说明:

参数/函数所属路由用途必填说明
getStaticPropsPages Router在构建时获取数据并注入页面 props是(SSG 页面)运行在 Node.js 环境,可访问文件系统、数据库等
getStaticPathsPages Router为动态路由(如 [id].js)生成所有可能的路径是(动态 SSG 页面)返回 { paths: [], fallback: false/true/'blocking' }
generateStaticParamsApp Router替代 getStaticPaths,为动态路由生成路径是(动态 SSG 页面)返回一个对象数组,每个对象对应一个路径参数
revalidate两者启用增量静态再生(ISR),设置秒级重新验证间隔revalidate: 60 表示每 60 秒后台重新生成一次
fallbackPages Router控制未预渲染路径的访问行为是(动态 SSG 页面)false:返回 404;true:客户端回退;'blocking':服务端回退

示例:动态路由 SSG 页面(Pages Router)

JSX
// pages/posts/[id].js
export async function getStaticPaths() {
  const res = await fetch('https://jsonplaceholder.typicode.com/posts');
  const posts = await res.json();

  const paths = posts.map((post) => ({
    params: { id: String(post.id) }, // 注意:id 必须是字符串
  }));

  return { paths, fallback: 'blocking' };
}

export async function getStaticProps({ params }) {
  const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${params.id}`);
  const post = await res.json();

  return {
    props: { post },
    revalidate: 60, // 启用 ISR,每 60 秒重新生成
  };
}

与同类方案对比

以下表格从 6 个关键维度对比 Next.js SSG、Gatsby、Hugo 和 Jekyll:

维度Next.js SSGGatsbyHugoJekyll
构建速度中等(大型站点需优化)慢(依赖 GraphQL 层)极快(Go 语言编写)快(Ruby 编写)
部署复杂度低(Vercel/Netlify 一键部署)中(需配置 Gatsby Cloud 或自建)低(纯静态文件)低(纯静态文件)
SEO 友好度极高(预渲染 + 元数据 API)极高(插件生态丰富)极高(原生支持)高(需手动配置)
动态内容支持强(混合渲染:SSG + SSR + ISR)弱(需插件或客户端渲染)无(纯静态)无(纯静态)
CDN 缓存效率极高(静态文件 + ISR 缓存)高(静态文件)极高(纯静态文件)极高(纯静态文件)
增量构建支持是(ISR / 按需 ISR)是(增量构建,但较慢)是(Hugo 0.112+)否(需全量构建)

选型建议

  • 需要混合渲染能力(同一应用同时使用 SSG、SSR、ISR):选 Next.js。这是其核心差异化优势。
  • 纯博客/文档站点,追求极致构建速度:选 Hugo。构建速度比 Next.js 快 10-100 倍。
  • 依赖 GraphQL 数据层,团队熟悉 React:选 Gatsby。但注意其构建性能瓶颈。
  • 简单静态站点,团队熟悉 Ruby:选 Jekyll。GitHub Pages 原生支持。

生产环境实践与注意事项

1. 构建时间优化

对于大型站点(如数千个页面),构建时间可能很长。以下策略可缓解:

  • 使用 ISR:在 getStaticProps 中设置 revalidate,让页面在首次请求时生成,而非全部在构建时生成。
  • 按需 ISR:通过 API 路由手动触发页面重新验证,避免定时轮询。
  • 增量构建:Next.js 14+ 支持增量构建,只重新构建变更的页面。
TSX
// 按需 ISR 示例(API 路由)
// pages/api/revalidate.js
export default async function handler(req, res) {
  if (req.query.secret !== process.env.REVALIDATION_SECRET) {
    return res.status(401).json({ message: 'Invalid token' });
  }

  try {
    await res.revalidate('/posts/1');
    return res.json({ revalidated: true });
  } catch (err) {
    return res.status(500).json({ message: 'Error revalidating' });
  }
}

2. 数据一致性与安全性

  • 不要在 getStaticProps 中暴露敏感数据:如 API 密钥、数据库凭证。应使用环境变量(process.env.API_KEY)。
  • 外部 API 调用必须有错误处理和超时机制:避免构建时因 API 故障导致整个构建失败。
TSX
export async function getStaticProps() {
  try {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 秒超时

    const res = await fetch('https://jsonplaceholder.typicode.com/posts', {
      signal: controller.signal,
    });
    clearTimeout(timeoutId);

    if (!res.ok) {
      throw new Error(`API responded with ${res.status}`);
    }

    const posts = await res.json();
    return { props: { posts } };
  } catch (error) {
    console.error('Failed to fetch posts:', error);
    return { props: { posts: [] } }; // 优雅降级
  }
}

3. 动态路由的路径生成

对于大量动态路由(如数百万个产品页面),构建时预渲染所有路径可能不现实。使用 fallback: 'blocking' 让首次请求时生成页面,后续请求直接返回缓存。

JSX
export async function getStaticPaths() {
  // 只预渲染热门产品的前 100 个
  const res = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=100');
  const posts = await res.json();

  const paths = posts.map((post) => ({
    params: { id: String(post.id) },
  }));

  return { paths, fallback: 'blocking' };
}

4. CDN 缓存策略

使用 CDN 时,注意缓存策略。对于 ISR 页面,设置 Cache-Control: s-maxage=60, stale-while-revalidate 可让 CDN 缓存 60 秒,并在后台重新验证。

常见报错与排查

错误信息原因解决方案
Error: getStaticPaths is required for dynamic SSG pages动态路由页面未导出 getStaticPathspages/posts/[id].js 中导出 getStaticPaths 并返回有效路径数组
Error: 'fetch' is not defined in getStaticPropsNode.js 版本低于 18,不支持原生 fetch升级 Node.js 到 18+,或安装 node-fetch / axios
Error: A required parameter (slug) was not provided as a string in getStaticPathsparams 中的参数值不是字符串类型确保使用 String() 转换,如 params: { slug: String(item.slug) }
Error: The 'fallback' key must be a boolean or 'blocking'fallback 字段值非法检查 fallback 值,必须是 falsetrue'blocking' 之一

常见问题 FAQ

Q: 如何在 Next.js SSG 中处理用户认证和个性化内容?

A: SSG 生成的页面是静态的,无法在构建时包含用户特定数据。对于需要认证的页面,建议使用客户端数据获取(如 useEffect + fetch)或服务端渲染(SSR)。也可以结合 NextAuth.js 等库,在客户端获取用户会话并动态渲染内容。

Q: 使用 SSG 时,如何更新已部署的静态页面?

A: 有三种方法:

  1. 全量重新部署:重新运行 next build 并部署整个应用。
  2. 增量静态再生(ISR):在 getStaticProps 中设置 revalidate 属性,让 Next.js 在后台重新生成页面。
  3. 按需 ISR:通过 API 路由手动触发页面重新验证,适合内容发布后立即更新。

Q: SSG 和 ISR 有什么区别?我应该如何选择?

A: SSG 在构建时生成所有页面,部署后内容不变。ISR 允许在运行时按需重新生成静态页面,结合了静态页面的性能和动态内容的灵活性。如果内容变化不频繁且可以接受构建时更新,选择 SSG。如果需要内容在部署后自动更新(如博客文章发布后立即生效),选择 ISR。

相关深度解决方案

在配置当前服务时,如果您需要实现更复杂的架构或多源数据整合,建议配合参考我们整理的 Nx 与 Turborepo 选型指南:大型团队与初创项目该如何抉择?

在配置当前服务时,如果您需要实现更复杂的架构或多源数据整合,建议配合参考我们整理的 解决 HMR 不生效与状态丢失:Webpack 热模块替换实战排查