渲染模式

了解 Nuxt 中可用的不同渲染模式。

Nuxt 支持不同的渲染模式,包括 通用渲染客户端渲染,同时也提供 混合渲染以及在 CDN 边缘服务器上渲染应用程序的可能性。

浏览器和服务器都可以解释 JavaScript 代码,将 Vue.js 组件转化为 HTML 元素。这个步骤称为 渲染。Nuxt 同时支持 通用客户端 渲染。这两种方法各有优缺点,我们接下来将逐一介绍。

默认情况下,Nuxt 使用 通用渲染 来提供更好的用户体验、性能并优化搜索引擎索引,但你只需通过 一行配置 即可切换渲染模式。

通用渲染

此步骤类似于 PHP 或 Ruby 应用程序执行的传统 服务端渲染。当浏览器请求启用了通用渲染的 URL 时,Nuxt 会在服务器环境中运行 JavaScript (Vue.js) 代码,并向浏览器返回一个完全渲染好的 HTML 页面。如果页面是提前生成的,Nuxt 也可以直接从缓存中返回完全渲染好的 HTML 页面。与客户端渲染相反,用户可以立即获取应用程序的全部初始内容。

一旦 HTML 文档下载完成,浏览器便会对其进行解析,随后 Vue.js 接管该文档。曾经在服务器上运行的同一段 JavaScript 代码,现在会在后台于客户端(浏览器)再次运行,通过将事件监听器绑定到 HTML 上来启用交互性(因此被称为 通用渲染)。这个过程称为 水合(Hydration)。水合完成后,页面便可享受动态接口和页面过渡等优势。

通用渲染使 Nuxt 应用程序能够提供快速的页面加载时间,同时保留客户端渲染的优点。此外,由于内容已经存在于 HTML 文档中,爬虫可以毫无开销地对其进行索引。

哪些是服务端渲染的,哪些是客户端渲染的?

在通用渲染模式下,很自然会有人问 Vue 文件的哪些部分在服务器和/或客户端运行。

app/app.vue
<script setup lang="ts">
const counter = ref(0) // executes in server and client environments

const handleClick = () => {
  counter.value++ // executes only in a client environment
}
</script>

<template>
  <div>
    <p>Count: {{ counter }}</p>
    <button @click="handleClick">
      Increment
    </button>
  </div>
</template>

在初次请求时,counter ref 在服务器端初始化,因为它是在 <p> 标签内渲染的。handleClick 的内容在这里永远不会执行。在浏览器端进行水合期间,counter ref 会被重新初始化。handleClick 最终会绑定到按钮上;因此,我们可以合理推断,handleClick 的函数体将始终在浏览器环境中运行。

中间件页面 在服务器上运行,并在水合期间在客户端运行。插件 可以在服务器、客户端或两者上渲染。组件 也可以被强制仅在客户端运行。组合式函数工具函数 则根据其使用的上下文进行渲染。

服务端渲染的优点

  • 性能:用户可以立即访问页面的内容,因为浏览器显示静态内容的速度远快于 JavaScript 生成的内容。同时,Nuxt 在水合过程中保留了 Web 应用程序的交互性。
  • 搜索引擎优化:通用渲染像经典的服务器应用程序一样,将页面的完整 HTML 内容交付给浏览器。网络爬虫可以直接索引页面的内容,这使得通用渲染成为任何需要快速索引的内容的绝佳选择。

服务端渲染的缺点

  • 开发限制:服务器和浏览器环境不提供相同的 API,编写能够无缝运行在双方的代码可能会比较棘手。幸运的是,Nuxt 提供了指南和特定变量来帮助你确定代码在哪一端执行。
  • 成本:需要运行服务器以便动态渲染页面。这会像任何传统服务器一样增加按月计算的成本。然而,由于采用了通用渲染并且由浏览器接管客户端导航,服务器调用次数大大减少。利用 边缘端渲染 可以降低成本。

通用渲染非常多功能,几乎可以适应任何用例,特别适合任何以内容为导向的网站:博客、营销网站、作品集、电商网站和市场。

有关编写不会引起水合不匹配的 Vue 代码的更多示例,请参阅 Vue 文档
当导入一个依赖于浏览器 API 且具有副作用的库时,请确保导入该库的组件仅在客户端被调用。打包工具不会对包含副作用的模块导入进行 Tree-shaking(摇树优化)。

客户端渲染

开箱即用,传统的 Vue.js 应用程序是在浏览器(即 客户端)中渲染的。然后,在浏览器下载并解析完包含创建当前界面指令的所有 JavaScript 代码后,Vue.js 生成 HTML 元素。

客户端渲染的优点

  • 开发速度:完全在客户端工作时,我们无需担心代码的服务器兼容性,例如使用诸如 window 对象之类仅限浏览器的 API。
  • 更便宜:运行服务器会增加基础设施成本,因为你需要在一个支持 JavaScript 的平台上运行。我们可以将纯客户端应用程序托管在任何带有 HTML、CSS 和 JavaScript 文件的静态服务器上。
  • 离线支持:由于代码完全在浏览器中运行,因此在没有网络连接的情况下它也能正常工作。

客户端渲染的缺点

  • 性能:用户必须等待浏览器下载、解析和运行 JavaScript 文件。根据下载的网络状况以及用于解析和执行的用户设备,这可能需要一些时间并影响用户体验。
  • 搜索引擎优化:与服务端渲染的 HTML 文档相比,索引和更新通过客户端渲染交付的内容需要更多时间。这与我们讨论过的性能缺点有关,因为搜索引擎爬虫在第一次尝试索引页面时不会等待界面完全渲染。使用纯客户端渲染时,你的内容将需要更多时间才能在搜索结果页中显示和更新。

客户端渲染是高度交互的 Web 应用程序的不错选择,这些应用程序不需要索引或用户访问频繁。它可以通过利用浏览器缓存在后续访问中跳过下载阶段,例如 SaaS、后台办公应用程序或在线游戏

你可以在 nuxt.config.ts 中通过 Nuxt 启用纯客户端渲染

nuxt.config.ts
export default defineNuxtConfig({
  ssr: false,
})
如果你确实使用了 ssr: false,你还应该在 ~/spa-loading-template.html 中放置一个 HTML 文件,其中包含你希望用来渲染加载屏幕的 HTML,该屏幕将在你的应用程序水合完成之前一直显示。
阅读更多关于 SPA 加载模板 的信息。

部署静态客户端渲染应用

如果你使用 nuxt generatenuxt build --prerender 命令将应用程序部署到 静态托管,那么默认情况下,Nuxt 会将每个页面渲染为单独的静态 HTML 文件。

如果你使用 nuxt generatenuxt build --prerender 命令预渲染你的应用,那么你将无法使用任何服务器端点,因为输出文件夹中不包含任何服务器。如果你需要服务器功能,请改用 nuxt build

如果你完全使用客户端渲染,这可能是没有必要的。你可能只需要一个单独的 index.html 文件,以及 200.html404.html 回退文件,你可以指示静态 Web 主机为所有请求提供这些文件。

为了实现这一点,我们可以更改路由的预渲染方式。只需将其添加到 nuxt.config.ts 中的 你的钩子(hooks)

nuxt.config.ts
export default defineNuxtConfig({
  hooks: {
    'prerender:routes' ({ routes }) {
      routes.clear() // Do not generate any routes (except the defaults)
    },
  },
})

这将生成三个文件

  • index.html
  • 200.html
  • 404.html

什么是 200.html 和 404.html?

静态主机需要一个 HTML 外壳来处理客户端路由和缺失的路径。为此,Nuxt 会生成两个 SPA 回退文件

  • 200.html。当你希望客户端路由器处理 URL 时,为不匹配的路径提供此文件。
  • 404.html。当主机应保持 404 状态但仍加载你的应用时,提供此文件。

nuxt generatenuxt build --prerender 会将这些文件写入 .output/public/。不带预渲染的普通 nuxt build 则不会。使用混合路由规则时,请通过路由规则添加回退,或者如果需要它们,请运行预渲染构建。将你的主机指向你的提供商期望的文件。

跳过客户端回退生成

在预渲染客户端渲染的应用时,Nuxt 默认会生成 index.html200.html404.html 文件。但是,如果你需要防止在构建中生成其中任何(或全部)文件,你可以使用来自 Nitro'prerender:generate' 钩子。

nuxt.config.ts
// @errors: 2353 7006
export default defineNuxtConfig({
  ssr: false,
  nitro: {
    hooks: {
      'prerender:generate' (route) {
        const routesToSkip = ['/index.html', '/200.html', '/404.html']
        if (routesToSkip.includes(route.route)) {
          route.skip = true
        }
      },
    },
  },
})

混合渲染

混合渲染允许通过 路由规则(Route Rules) 为每个路由设置不同的缓存规则,并决定服务器应如何响应给定 URL 上的新请求。

以前,Nuxt 应用程序和服务器的每个路由/页面必须使用相同的渲染模式(通用或客户端渲染)。在各种情况下,某些页面可以在构建时生成,而其他页面则应该进行客户端渲染。例如,想象一个带有管理后台的内容网站。每个内容页面应该是主要静态的并生成一次,但管理后台需要注册并且更像是一个动态应用程序。

Nuxt 包含路由规则和混合渲染支持。使用路由规则,你可以为一组 Nuxt 路由定义规则、更改渲染模式或基于路由分配缓存策略!

Nuxt 服务器将自动注册相应的中间件,并使用 Nitro 缓存层用缓存处理器包装路由。

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    // Homepage pre-rendered at build time
    '/': { prerender: true },
    // Products page generated on demand, revalidates in background, cached until API response changes
    '/products': { swr: true },
    // Product pages generated on demand, revalidates in background, cached for 1 hour (3600 seconds)
    '/products/**': { swr: 3600 },
    // Blog posts page generated on demand, revalidates in background, cached on CDN for 1 hour (3600 seconds)
    '/blog': { isr: 3600 },
    // Blog post page generated on demand once until next deployment, cached on CDN
    '/blog/**': { isr: true },
    // Admin dashboard renders only on client-side
    '/admin/**': { ssr: false },
    // Add cors headers on API routes
    '/api/**': { cors: true },
    // Redirects legacy urls
    '/old-page': { redirect: '/new-page' },
  },
})

路由规则

你可以使用以下不同的属性

  • redirect: string - 定义服务端重定向。
  • ssr: boolean - 禁用应用某些部分的 HTML 服务端渲染,并通过 ssr: false 使它们仅在浏览器中渲染
  • cors: boolean - 通过 cors: true 自动添加 cors 标头 - 你可以通过用 headers 覆盖来自定义输出
  • headers: object - 为网站的各个部分添加特定的标头 - 例如,你的静态资产
  • swr: number | boolean - 向服务器响应添加缓存标头,并在服务器或反向代理上以可配置的 TTL(生存时间)对其进行缓存。Nitro 的 node-server 预设能够缓存完整的响应。当 TTL 过期时,将发送缓存的响应,同时在后台重新生成页面。如果使用 true,则会添加不带 MaxAge 的 stale-while-revalidate 标头。
  • isr: number | boolean - 其行为与 swr 相同,不同之处在于我们能够将响应添加到支持此功能的平台(目前为 Netlify 或 Vercel)上的 CDN 缓存中。如果使用 true,则内容将一直持久化到 CDN 内的下一次部署。
  • prerender: boolean - 在构建时预渲染路由,并将其作为静态资源包含在你的构建中
  • noScripts: boolean - 禁用网站某些部分的 Nuxt 脚本和 JS 资源提示的渲染。
  • appMiddleware: string | string[] | Record<string, boolean> - 允许你定义应该或不应该为应用程序中 Vue 应用部分的页面路径运行的中间件(即不是你的 Nitro 路由)
使用 isrswr 的路由还会在生成 HTML 的同时生成 _payload.json 文件。客户端导航会加载这些缓存的 payload,而不是重新获取数据。阅读更多关于 payload 提取 的信息。

只要有可能,路由规则就会自动应用到部署平台的原生规则上以获得最佳性能(目前支持 Netlify 和 Vercel)。

请注意,当使用 nuxt generate 时,混合渲染不可用。

示例

Nuxt Vercel ISR

在 Vercel 上部署的带有混合渲染的 Nuxt 应用程序示例。

边缘端渲染

边缘端渲染(ESR)是 Nuxt 中引入的一项强大功能,它允许通过内容分发网络(CDN)的边缘服务器,在更靠近用户的地方渲染你的 Nuxt 应用程序。通过利用 ESR,你可以确保提高性能并减少延迟,从而提供增强的用户体验。

通过 ESR,渲染过程被推送到网络的“边缘”——CDN 的边缘服务器。与其说 ESR 是一个实际的渲染模式,不如说它是一个部署目标。

当请求页面时,它不会一直发送到源服务器,而是被最近的边缘服务器拦截。该服务器为页面生成 HTML 并将其发回给用户。此过程最大限度地减少了数据必须传输的物理距离,从而减少了延迟并更快地加载页面

边缘端渲染得以实现,要归功于驱动 Nuxt 的服务器引擎 Nitro。它为 Node.js、Deno、Cloudflare Workers 等提供了跨平台支持。

你可以利用 ESR 的当前平台有:

请注意,当将边缘端渲染与路由规则结合使用时,可以使用 混合渲染