SSG 静态站点生成与预渲染

不是所有页面都需要每次请求都渲染——营销落地页、博客文章、帮助文档这些内容变化不频繁的页面,在构建时生成静态 HTML 就够了。SSG 将 SSR 的能力前移到构建阶段,产出纯静态文件,可以部署到任何 CDN,零服务器成本、极致速度。本章讲清 Nuxt4 的 SSG 工作流、预渲染策略、ISR 增量再生,以及 AI 视频落地页的 SEO 实战。

1. nuxi generate 工作流

1.1 SSG 的核心思路

构建时(Build Time)             运行时(Runtime)
─────────────────────          ──────────────────
nuxi generate                  CDN / Static Hosting
  ↓                              ↓
① 构建应用                      用户请求 /about
② 爬取所有路由                     ↓
③ 逐个路由执行 SSR              直接返回 about.html
④ 输出 .output/public/         (无需服务器、零延迟)
   ├── index.html
   ├── about.html
   ├── videos/index.html
   └── _nuxt/ (JS/CSS)

1.2 使用 nuxi generate

# 生成静态站点
npx nuxi generate
 
# 等价于
npx nuxi build --prerender

输出目录:.output/public/——所有 HTML、JS、CSS 都是纯静态文件。

1.3 SSG vs SSR 对比

特性SSG (nuxi generate)SSR (nuxi build)
渲染时机构建时每次请求时
部署要求静态托管(CDN、GitHub Pages)需要 Node.js 服务器
首屏速度极快(直接返回 HTML)快(服务端渲染后返回)
数据新鲜度构建时的快照实时
服务器成本有(CPU + 内存)
适用场景博客、文档、落地页动态内容、用户个性化页面

2. routeRules prerender 配置

2.1 在 nuxt.config.ts 中配置预渲染

// nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    // 指定路由在构建时预渲染
    '/': { prerender: true },
    '/about': { prerender: true },
    '/pricing': { prerender: true },
    '/blog/**': { prerender: true },    // 通配符:所有博客页面
 
    // 排除某些路由不预渲染
    '/studio/**': { prerender: false },  // 工作台不需要预渲染
    '/admin/**': { prerender: false },   // 管理后台不需要预渲染
  },
})

2.2 使用 definePageMeta 标记

也可以在页面组件中标记是否预渲染:

<!-- app/pages/about.vue -->
<script setup lang="ts">
defineRouteRules({
  prerender: true,  // 构建时预渲染此页面
})
</script>

2.3 nuxt.config 中的 nitro.prerender 配置

// nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    prerender: {
      // 入口路由(爬虫从这些页面开始)
      routes: ['/', '/sitemap.xml'],
 
      // 额外爬取这些路由
      crawlLinks: true,  // 自动发现 <a> 链接
 
      // 忽略某些路由
      ignore: [
        '/api/**',        // 不预渲染 API 路由
        '/studio/**',     // 不预渲染工作台
      ],
 
      // 并发数(加快构建速度)
      concurrency: 10,
 
      // 失败时不中断构建
      failOnError: false,
    },
  },
})

3. 动态路由预渲染爬取策略

crawlLinks: true 时,Nuxt 会从入口页面开始,递归发现所有 &lt;a&gt;&lt;NuxtLink&gt; 链接:

入口 /
  ├── 发现链接 /about → 预渲染 /about
  ├── 发现链接 /blog → 预渲染 /blog
  │   ├── 发现链接 /blog/post-1 → 预渲染 /blog/post-1
  │   └── 发现链接 /blog/post-2 → 预渲染 /blog/post-2
  └── 发现链接 /videos → 预渲染 /videos
      ├── 发现链接 /videos/abc → 预渲染 /videos/abc
      └── ...

限制:爬虫只能发现 HTML 中静态存在的链接。如果链接是通过 JavaScript 动态生成的(如无限滚动列表),爬虫无法发现。

3.2 手动指定动态路由

对于爬虫无法自动发现的动态路由,手动指定:

// nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    prerender: {
      routes: [
        '/',
        '/videos/abc123',
        '/videos/def456',
        '/videos/ghi789',
      ],
    },
  },
})

3.3 从 API 动态获取路由列表

更优雅的方式是在构建时从 API 获取所有需要预渲染的路由:

// nuxt.config.ts
export default defineNuxtConfig({
  hooks: {
    async 'prerender:routes'(ctx) {
      // 从 API 获取所有视频 ID
      const videos = await fetch('https://api.example.com/videos?fields=id')
        .then(res => res.json())
 
      // 添加到预渲染列表
      for (const video of videos) {
        ctx.routes.add(`/videos/${video.id}`)
      }
 
      // 从 CMS 获取所有博客文章
      const posts = await fetch('https://cms.example.com/posts?fields=slug')
        .then(res => res.json())
 
      for (const post of posts) {
        ctx.routes.add(`/blog/${post.slug}`)
      }
    },
  },
})

3.4 生成 sitemap.xml

配合 @nuxtjs/sitemap 模块自动生成站点地图:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxtjs/sitemap'],
  site: { url: 'https://aivideo.example.com' },
  sitemap: {
    // 动态路由通过 API 获取
    sources: ['/api/__sitemap__/urls'],
  },
})
// server/api/__sitemap__/urls.ts
export default defineSitemapEventHandler(async () => {
  const videos = await fetchVideoList()
  return videos.map(v => ({
    loc: `/videos/${v.id}`,
    lastmod: v.updatedAt,
    changefreq: 'weekly',
    priority: 0.8,
  }))
})

4. 增量静态再生(ISR)原理

4.1 ISR 解决了什么问题

SSG 的问题:数据在构建时就固定了,内容更新需要重新构建整个站点。

ISR(Incremental Static Regeneration)的方案:

首次请求 /videos/123
  → 缓存中没有 → 服务端 SSR → 返回 HTML → 缓存 HTML(TTL = 60s)

60 秒内的后续请求
  → 缓存命中 → 直接返回缓存的 HTML(极快)

60 秒后的请求
  → 缓存过期 → 返回旧缓存(stale) → 后台异步重新渲染 → 更新缓存

4.2 在 Nuxt4 中配置 ISR

// nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    // ISR:缓存 60 秒后自动刷新
    '/videos/**': {
      isr: 60,  // 60 秒后重新验证
    },
 
    // ISR:缓存 1 小时
    '/blog/**': {
      isr: 3600,
    },
 
    // 永久缓存(等同于 SSG,但按需生成)
    '/docs/**': {
      isr: true,  // 永不过期
    },
 
    // SWR 模式:立即返回缓存,后台刷新
    '/categories/**': {
      swr: 300,  // 缓存 5 分钟
    },
  },
})

4.3 ISR vs SSG vs SSR

特性SSGISRSSR
渲染时机构建时首次请求时 + 定期刷新每次请求
数据新鲜度构建时快照近实时(TTL 控制)实时
响应速度极快(静态文件)极快(缓存命中时)一般
服务器要求有(但负载很低)
新增内容需要重新构建自动按需生成自动

4.4 ISR 部署要求

ISR 需要一个支持缓存的运行时环境:

  • Vercel:原生支持
  • Netlify:通过 Edge Functions 支持
  • Cloudflare Workers:通过 KV 存储实现
  • 自建服务器:需要配置缓存层(Redis / 文件缓存)

5. AI 视频落地页 SSG + SEO 实战

5.1 页面规划

需要 SSG 的页面(内容稳定、SEO 重要):
├── /                    ← 首页落地页
├── /about               ← 关于我们
├── /pricing             ← 定价页
├── /features            ← 功能介绍
├── /blog/**             ← 博客文章
└── /help/**             ← 帮助文档

需要 ISR 的页面(内容会更新、SEO 重要):
├── /videos/**           ← 视频详情页(ISR 60s)
└── /categories/**       ← 分类页(ISR 300s)

需要 SSR 的页面(个性化、实时数据):
├── /studio/**           ← 创作工作台
├── /admin/**            ← 管理后台
└── /search              ← 搜索结果页

5.2 完整 routeRules 配置

// nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    // SSG:构建时预渲染
    '/': { prerender: true },
    '/about': { prerender: true },
    '/pricing': { prerender: true },
    '/features': { prerender: true },
    '/blog/**': { prerender: true },
    '/help/**': { prerender: true },
 
    // ISR:缓存 + 定期刷新
    '/videos/**': { isr: 60 },
    '/categories/**': { swr: 300 },
 
    // SSR:每次请求实时渲染
    '/studio/**': { ssr: true },
    '/admin/**': { ssr: true },
 
    // SPA:客户端渲染(不需要 SEO 的页面)
    '/settings/**': { ssr: false },
  },
})

5.3 SEO 优化:useHead + useSeoMeta

<!-- app/pages/videos/[id].vue -->
<script setup lang="ts">
const route = useRoute()
const { data: video } = await useFetch(`/api/videos/${route.params.id}`)
 
if (!video.value) {
  throw createError({ statusCode: 404, message: '视频不存在' })
}
 
// SEO 元信息
useSeoMeta({
  title: `${video.value.title} - AI 视频平台`,
  description: video.value.description?.slice(0, 160),
  ogTitle: video.value.title,
  ogDescription: video.value.description?.slice(0, 160),
  ogImage: video.value.thumbnail,
  ogType: 'video.other',
  twitterCard: 'summary_large_image',
})
 
// 结构化数据(JSON-LD)
useHead({
  script: [{
    type: 'application/ld+json',
    innerHTML: JSON.stringify({
      '@context': 'https://schema.org',
      '@type': 'VideoObject',
      name: video.value.title,
      description: video.value.description,
      thumbnailUrl: video.value.thumbnail,
      uploadDate: video.value.createdAt,
      duration: `PT${video.value.duration}S`,
      contentUrl: video.value.url,
    }),
  }],
})
</script>

5.4 落地页 SSG 示例

<!-- app/pages/index.vue — 首页落地页 -->
<script setup lang="ts">
defineRouteRules({ prerender: true })
 
useSeoMeta({
  title: 'AI 视频平台 - 用 AI 创造精彩视频',
  description: '基于最新 AI 技术,一键生成、编辑、发布高质量视频内容。',
  ogImage: '/images/og-home.jpg',
})
 
// 构建时获取精选视频(数据会嵌入静态 HTML)
const { data: featured } = await useFetch('/api/videos/featured', {
  pick: ['id', 'title', 'thumbnail', 'views'],
})
</script>
 
<template>
  <div>
    <HeroSection />
    <FeaturedVideos :videos="featured" />
    <FeatureShowcase />
    <PricingTable />
    <CTASection />
  </div>
</template>

本章小结

  • nuxi generate:构建时预渲染所有页面,输出纯静态 HTML
  • routeRules prerender:精确控制哪些路由需要预渲染
  • crawlLinks:自动爬取 &lt;a&gt; 链接发现路由;动态路由需手动指定或从 API 获取
  • ISR:首次请求渲染 + 缓存 + 定期刷新,兼顾性能和数据新鲜度
  • SEOuseSeoMeta 设置 OG 标签,useHead 注入 JSON-LD 结构化数据
  • 实战:落地页用 SSG、视频详情用 ISR、工作台用 SSR——按场景选择最佳渲染策略

下一章将这三种渲染模式统一起来——混合渲染架构设计。