预渲染

Nuxt 允许在构建时静态渲染页面,以优化性能或 SEO 指标。

Nuxt 允许在构建时预渲染应用中的指定页面。请求时 Nuxt 会直接返回预构建的页面,而不是动态生成。

基于爬虫的预渲染

使用 nuxt generate 命令 来构建和预渲染应用,它使用 Nitro 爬虫。这个命令等同于 nuxt build 设置 nitro.static 为 true,或者运行 nuxt build --prerender。

它会构建站点、启动一个 Nuxt 实例,默认预渲染根页面 /,以及站点中链接到的所有页面、这些页面再链接到的页面,依此类推。

npx nuxt generate
yarn nuxt generate
pnpm nuxt generate
bun x nuxt generate
deno x nuxt generate

现在你可以把 .output/public 目录部署到任何静态托管服务,或者本地用 npx serve .output/public 预览。

静态和预渲染构建还会生成 200.html 和 404.html SPA 兜底页面。参考什么是 200.html 和 404.html?。

Nitro 爬虫的工作方式:

  1. 加载应用根路由(/)的 HTML、~/pages 目录中的所有非动态页面,以及 prerender.routes 数组中的其他路由。
  2. 将 HTML 和 _payload.json 保存到 ~/.output/public/ 目录,用于静态服务。
  3. 在 HTML 中找到所有锚标签(<a href="...">),导航到其他路由。
  4. 对每个找到的锚标签重复步骤 1-3,直到没有更多可爬的锚标签。

理解这一点很重要,因为没有被可发现页面链接到的页面无法自动预渲染。

阅读更多关于 nuxt generate 命令的内容。

选择性预渲染

你可以在 nuxt.config 文件中手动指定需要预渲染的路由,或者忽略不想预渲染的路由(比如 /dynamic):

export default defineNuxtConfig({
  prerender: {
    routes: ['/user/1', '/user/2'],
    ignore: ['/dynamic'],
  },
})

你可以结合 crawlLinks 选项来预渲染爬虫无法发现的路由,比如 /sitemap.xml 或 /robots.txt:

export default defineNuxtConfig({
  prerender: {
    crawlLinks: true,
    routes: ['/sitemap.xml', '/robots.txt'],
  },
})

顶层的 prerender 选项适用于任何 server.builder。构建器特定的选项(比如 Nitro 的 concurrency 或 failOnError)可以在 nitro.prerender 中设置。

阅读更多关于 Nitro 预渲染的内容。

最后,你也可以用 routeRules 手动配置。

export default defineNuxtConfig({
  routeRules: {
    // 设置 prerender 为 true 来预渲染
    '/rss.xml': { prerender: true },
    // 设置为 false 来跳过预渲染
    '/this-DOES-NOT-get-prerendered': { prerender: false },
    // /blog/ 下的所有内容都会被预渲染,
    // 只要它被其他页面链接到
    '/blog/**': { prerender: true },
  },
})

阅读更多关于 Nitro routeRules 配置的内容。

作为简写,你也可以在页面文件中使用 defineRouteRules 配置。

这个功能是实验性的,要使用它必须在 nuxt.config 中启用 experimental.inlineRouteRules 选项。

<script setup>
// 或者在页面级别设置
defineRouteRules({
  prerender: true,
})
</script>

<template>
  <div>
    <h1>首页</h1>
    <p>构建时预渲染</p>
  </div>
</template>

这会被转换为:

export default defineNuxtConfig({
  routeRules: {
    '/': { prerender: true },
  },
})

Payload 提取

当 Nuxt 在服务端渲染页面时,它会将数据获取结果(useAsyncData 和 useFetch)和应用状态(useState)序列化为 payload,这样客户端水合时就不需要重新获取。启用 payload 提取后,Nuxt 还会将这个 payload 写入路由 HTML 旁边的 _payload.json 文件:

  • 预渲染的路由在构建时生成 payload 文件。
  • 使用 ISR 或 SWR 缓存的路由在第一次渲染时生成 payload 文件,即使是在混合(非静态)站点上。

客户端导航时,Nuxt 会获取目标路由的 _payload.json 文件,复用提取的数据,而不是在浏览器中重新执行数据获取。

你可以通过 experimental.payloadExtraction 选项控制这个行为:

  • 'client' - payload 内联在 HTML 中用于初始渲染,客户端导航时提取为 _payload.json 文件。首次加载没有额外网络请求。
  • true - 初始渲染和客户端导航都将 payload 提取到单独的 _payload.json 文件。HTML 更小,payload 文件可以被 CDN 缓存,代价是首次加载多一个请求。
  • false - 禁用 payload 提取。payload 始终内联在 HTML 中,不生成 _payload.json 文件。

默认是 true,设置 compatibilityVersion: 5 时为 'client'。设置 ssr: false 时强制为 false。

export default defineNuxtConfig({
  experimental: {
    payloadExtraction: 'client',
  },
})

需要注意几个实际影响:

  • 在完全静态的站点上,客户端导航复用构建时捕获的数据,所以在下次重新构建之前数据可能是过期的。
  • 对于 ISR/SWR 路由,CDN 可以缓存 payload 文件和 HTML 一起,提升缓存路由的客户端导航性能。像 pages/[...slug].vue 这样的动态路由可以用 glob 模式 opt-in,比如 '/**': { isr: true }。
  • Payload 使用 devalue 序列化,所以自定义类型(比如类实例)需要 payload 插件配合自定义 reducer 和 reviver 才能正确往返。

运行时预渲染配置

prerenderRoutes

你可以在 Nuxt 上下文中运行时调用它,添加更多需要 Nitro 预渲染的路由。

<script setup>
prerenderRoutes(['/some/other/url'])
prerenderRoutes('/api/content/article/my-article')
</script>

<template>
  <div>
    <h1>预渲染时会注册其他路由</h1>
  </div>
</template>

prerender:routes Nuxt hook

在预渲染之前调用,用于注册更多路由。

export default defineNuxtConfig({
  hooks: {
    async 'prerender:routes' (ctx) {
      const { pages } = await fetch('https://api.some-cms.com/pages').then(
        res => res.json(),
      )
      for (const page of pages) {
        ctx.routes.add(`/${page.name}`)
      }
    },
  },
})

prerender:generate Nitro hook

预渲染过程中每个路由都会调用。你可以用它来精细处理每个预渲染的路由。

export default defineNuxtConfig({
  nitro: {
    hooks: {
      'prerender:generate' (route) {
        if (route.route?.includes('private')) {
          route.skip = true
        }
      },
    },
  },
})