渲染模式

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

Nuxt 支持多种渲染模式,包括 通用渲染 (Universal Rendering)客户端渲染 (Client-Side Rendering),同时也提供了 混合渲染 (Hybrid Rendering),并支持在 CDN 边缘服务器上渲染您的应用程序。

浏览器和服务器都可以解析 JavaScript 代码,将 Vue.js 组件转换为 HTML 元素。这一步骤称为渲染 (Rendering)。Nuxt 同时支持通用 (Universal)客户端 (Client-side) 渲染。这两种方法各有利弊,我们将在下文中详细介绍。

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

通用渲染 (Universal Rendering)

这一步类似于 PHP 或 Ruby 应用程序执行的传统服务端渲染 (Server-side rendering)。当浏览器请求启用了通用渲染的 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 的主体将始终在浏览器环境中运行。

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

服务端渲染的优势

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

服务端渲染的劣势

  • 开发约束: 服务器和浏览器环境并不提供相同的 API,编写能够无缝运行在两端的代码可能会很棘手。幸运的是,Nuxt 提供了指南和特定变量来帮助您确定一段代码是在哪里执行的。
  • 成本: 需要一台服务器来实时渲染页面。这像任何传统服务器一样增加了每月成本。然而,由于通用渲染在客户端导航时由浏览器接管,服务器调用被大大减少了。利用 边缘渲染 (Edge-side-rendering) 可以降低成本。

通用渲染非常灵活,几乎适用于任何用例,特别适合以内容为导向的网站:博客、营销网站、个人作品集、电子商务网站和市场平台。

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

客户端渲染 (Client-Side Rendering)

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

客户端渲染的优势

  • 开发速度:当完全在客户端开发时,我们不必担心代码的服务器兼容性,例如使用 window 对象等仅限浏览器的 API。
  • 成本较低: 运行服务器会增加基础设施成本,因为您需要在支持 JavaScript 的平台上运行。我们可以将仅客户端的应用程序托管在任何带有 HTML、CSS 和 JavaScript 文件的静态服务器上。
  • 离线: 因为代码完全在浏览器中运行,所以即使在没有网络的情况下,它也能保持良好运行。

客户端渲染的劣势

  • 性能:用户必须等待浏览器下载、解析并运行 JavaScript 文件。根据下载的网络情况以及用户设备处理解析和执行的能力,这可能需要一些时间并影响用户体验。
  • 搜索引擎优化 (SEO):相比于服务端渲染的 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.html404.html 对您使用的托管服务提供商可能很有用。

跳过客户端回退生成

预渲染客户端渲染的应用程序时,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 相同,不同之处在于我们能够将响应添加到支持此功能的平台的 CDN 缓存中(目前支持 Netlify 或 Vercel)。如果使用 true,则内容在 CDN 中会一直存在,直到下一次部署。
  • prerender: boolean - 在构建时预渲染路由,并将它们作为静态资源包含在您的构建中。
  • noScripts: boolean - 禁用站点部分内容的 Nuxt 脚本渲染和 JS 资源提示。
  • appMiddleware: string | string[] | Record<string, boolean> - 允许您定义对于应用程序的 Vue 部分(即非 Nitro 路由)内的页面路径应该或不应该运行的中间件。
使用 isrswr 的路由也会在 HTML 旁边生成 _payload.json 文件。客户端导航会加载这些缓存的负载,而不是重新获取数据。使用全局模式配置动态路由,如 pages/[...slug].vue'/**': { isr: true }

只要可能,路由规则将自动应用于部署平台的原生规则,以实现最佳性能(目前支持 Netlify 和 Vercel)。

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

示例

Nuxt Vercel ISR

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

边缘渲染 (Edge-Side Rendering)

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

通过 ESR,渲染过程被推向网络的“边缘”——CDN 的边缘服务器。请注意,ESR 更像是一个部署目标,而不是一个实际的渲染模式。

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

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

您可以利用 ESR 的当前平台包括:

注意,当使用带有路由规则的边缘渲染时,可以使用混合渲染