渲染模式

了解 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) // 在服务器和客户端环境中执行

const handleClick = () => {
  counter.value++ // 仅在客户端环境中执行
}
</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 内容作为经典服务器应用程序交付给浏览器。Web 爬虫可以直接索引页面的内容,这使得通用渲染成为任何想要快速索引的内容的绝佳选择。

服务器端渲染的缺点:

  • 开发约束:服务器和浏览器环境不提供相同的 API,编写可以在两侧无缝运行的代码可能会很棘手。幸运的是,Nuxt 提供了指南和特定变量来帮助您确定代码在哪里执行。
  • 成本:需要运行服务器才能动态渲染页面。这像任何传统服务器一样增加了每月成本。但是,由于通用渲染,服务器调用大大减少,浏览器接管客户端导航。

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

有关编写没有水合不匹配的 Vue 代码的更多示例,请参阅 Vue 文档
当导入一个依赖浏览器 API 并有副作用的库时,请确保导入它的组件只在客户端调用。打包器不会摇树包含副作用的模块的导入。

客户端渲染

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

客户端渲染的好处:

  • 开发速度:当完全在客户端工作时,我们不必担心代码的服务器兼容性,例如,使用仅浏览器的 API,如 window 对象。
  • 更便宜:运行服务器增加了基础设施成本,因为您需要在支持 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 中的您的钩子:

nuxt.config.ts
export default defineNuxtConfig({
  hooks: {
    'prerender:routes' ({ routes }) {
      routes.clear() // 不生成任何路由(除了默认路由)
    },
  },
})

这将生成三个文件:

  • 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
        }
      },
    },
  },
})

混合渲染

混合渲染允许使用路由规则为每个路由使用不同的缓存规则,并决定服务器应如何响应给定 URL 的新请求。

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

Nuxt 包括路由规则和混合渲染支持。使用路由规则,您可以为一组 nuxt 路由定义规则,根据路由更改渲染模式或分配缓存策略!

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

nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    // 首页在构建时预渲染
    '/': { prerender: true },
    // 产品页面按需生成,在后台重新验证,直到 API 响应更改之前缓存
    '/products': { swr: true },
    // 产品页面按需生成,在后台重新验证,缓存 1 小时(3600 秒)
    '/products/**': { swr: 3600 },
    // 博客文章页面按需生成,在后台重新验证,在 CDN 上缓存 1 小时(3600 秒)
    '/blog': { isr: 3600 },
    // 博客文章页面按需生成一次,直到下次部署,在 CDN 上缓存
    '/blog/**': { isr: true },
    // 管理仪表板仅在客户端渲染
    '/admin/**': { ssr: false },
    // 在 API 路由上添加 cors 标头
    '/api/**': { cors: true },
    // 重定向旧 URL
    '/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 应用程序示例。

::

边缘渲染

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

使用 ESR,渲染过程被推送到网络的"边缘" - CDN 的边缘服务器。请注意,ESR 更多是一个部署目标,而不是实际的渲染模式。

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

边缘渲染之所以可能,归功于 Nitro,它为 Node.js、Deno、Cloudflare Workers 等提供跨平台支持。

您目前可以利用 ESR 的平台有:

请注意,当使用边缘渲染时,可以使用混合渲染配合路由规则。