渲染模式
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 文件的哪些部分在服务器和/或客户端运行,这是很正常的。
<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 被渲染在 <p> 标签内部,因此它会在服务器上初始化。handleClick 的内容在此处不会被执行。在浏览器进行水合时,counter ref 会被重新初始化。handleClick 最终会绑定到按钮上;因此可以合理地推断 handleClick 的主体将始终在浏览器环境中运行。
中间件 和 页面 会在服务器上运行,并在水合期间在客户端运行。插件 可以在服务器或客户端或两者上运行。组件 也可以被强制仅在客户端运行。组合式函数(Composables) 和 工具函数 则根据它们的使用上下文决定运行位置。
服务器端渲染的好处:
- 性能:用户可以立即访问页面内容,因为浏览器显示静态内容要比显示由 JavaScript 生成的内容更快。与此同时,Nuxt 在水合过程中保留了 Web 应用的交互性。
- 搜索引擎优化:通用渲染将页面的完整 HTML 内容像传统服务器应用一样交付给浏览器。网络爬虫可以直接索引页面内容,这使得通用渲染成为希望快速被索引内容的优秀选择。
服务器端渲染的缺点:
- 开发限制:服务器和浏览器环境提供的 API 并不相同,编写可以无缝在两端运行的代码可能比较棘手。幸运的是,Nuxt 提供了指南和特定变量来帮助你确定某段代码在哪执行。
- 成本:需要有服务器运行以便实时渲染页面。这会像任何传统服务器一样增加月度成本。然而,由于通用渲染使得浏览器在客户端导航时接手,服务器调用得到了大量减少。通过利用边缘端渲染 可以进一步降低成本。
通用渲染非常灵活,几乎适用于任何用例,尤其适合面向内容的网站:博客、营销网站、作品集、电子商务网站和市场平台。
客户端渲染
传统的 Vue.js 应用默认是在浏览器(或“客户端”)中渲染的。然后,Vue.js 在浏览器下载并解析包含创建当前界面的指令的所有 JavaScript 代码后生成 HTML 元素。
客户端渲染的好处:
- 开发速度:在完全客户端上开发时,我们不必担心代码的服务器兼容性,例如使用仅属于浏览器的 API(如
window对象)。 - 更便宜:运行服务器会增加基础设施成本,因为你需要在支持 JavaScript 的平台上运行。我们可以在任何静态服务器上托管仅客户端应用,提供 HTML、CSS 和 JavaScript 文件即可。
- 离线:由于代码全部在浏览器中运行,当网络不可用时它仍能良好工作。
客户端渲染的缺点:
- 性能:用户必须等待浏览器下载、解析并运行 JavaScript 文件。根据网络下载和用户设备的解析与执行速度,这可能需要一些时间并影响用户体验。
- 搜索引擎优化:通过客户端渲染交付的内容的索引和更新比服务器渲染的 HTML 文档更耗时。这与我们讨论的性能缺点有关,因为搜索引擎爬虫不会在第一次尝试时等待界面完全渲染来索引页面。使用纯客户端渲染时,你的内容在搜索结果页面中显示和更新会更慢。
客户端渲染适合高度交互的 Web 应用,这些应用不需要被索引或用户访问频繁。它可以利用浏览器缓存在后续访问中跳过下载阶段,例如 SaaS、后台管理应用或在线游戏。
你可以在 nuxt.config.ts 中启用仅客户端渲染:
export default defineNuxtConfig({
ssr: false,
})
ssr: false,你还应该在 ~/spa-loading-template.html 中放置一个 HTML 文件,用你希望用于渲染加载屏幕的 HTML 内容,该加载屏幕会在应用水合之前显示。部署静态客户端渲染应用
如果你使用 nuxt generate 或 nuxt build --prerender 命令将应用部署到静态托管,那么默认情况下,Nuxt 会将每个页面渲染为单独的静态 HTML 文件。
nuxt generate 或 nuxt build --prerender 对应用进行预渲染,那么输出文件夹中将不会包含任何服务器,因此你将无法使用任何服务器端点。如果你需要服务器功能,请改用 nuxt build。如果你完全使用客户端渲染,那么这可能是不必要的。你可能只需要一个 index.html 文件,以及 200.html 和 404.html 回退文件,并可以让你的静态网页托管服务为所有请求返回这些文件。
为此,我们可以改变路由的预渲染方式。只需在 nuxt.config.ts 的 hooks 中添加如下内容:
export default defineNuxtConfig({
hooks: {
'prerender:routes' ({ routes }) {
routes.clear() // 不生成任何路由(除了默认项)
},
},
})
这将生成三个文件:
index.html200.html404.html
200.html 和 404.html 可能对你使用的托管提供商有用。
跳过客户端回退生成
在对客户端渲染的应用进行预渲染时,Nuxt 默认会生成 index.html、200.html 和 404.html 文件。但是,如果你需要在构建中阻止生成这些文件中的任意一个(或全部),可以使用 Nitro 的 'prerender:generate' 钩子。
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 缓存层 为路由封装缓存处理程序。
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 },
// 重定向旧链接
'/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,会添加一个stale-while-revalidate头但没有 MaxAge。isr: number | boolean- 行为与swr相同,不同之处在于我们可以在支持的平台上将响应添加到 CDN 缓存(当前支持 Netlify 或 Vercel)。如果使用true,内容将在 CDN 内部持续存在直到下一次部署。prerender: boolean- 在构建时预渲染路由并将其作为静态资源包含在构建中。noScripts: boolean- 禁用在站点某些部分渲染 Nuxt 脚本和 JS 资源提示。appMiddleware: string | string[] | Record<string, boolean>- 允许你为应用中 Vue 部分的页面路径(即非 Nitro 路由)定义应当运行或不运行的中间件。
在可能的情况下,路由规则将自动应用于部署平台的原生规则以获得最佳性能(目前支持 Netlify 和 Vercel)。
nuxt generate 时不支持混合渲染。示例:
边缘端渲染
边缘端渲染(Edge-Side Rendering,ESR)是 Nuxt 引入的一项强大功能,它允许通过内容分发网络(CDN)的边缘服务器更接近用户地渲染你的 Nuxt 应用。通过利用 ESR,你可以确保性能提升和延迟降低,从而提供更好的用户体验。
使用 ESR 时,渲染过程被推送到网络的“边缘”——CDN 的边缘服务器。注意,ESR 更像是一个部署目标,而不是一种实际的渲染模式。
当发出页面请求时,请求不会一路到达原始服务器,而是在最近的边缘服务器被拦截。该服务器为页面生成 HTML 并将其发送回用户。此过程将数据需要传输的物理距离最小化,从而“降低延迟并更快加载页面”。
边缘端渲染之所以成为可能,是因为有 Nitro,即驱动 Nuxt 的服务引擎。它为 Node.js、Deno、Cloudflare Workers 等提供跨平台支持。
当前可以利用 ESR 的平台有:
- 使用 git 集成和
nuxt build命令的 Cloudflare Pages,无需任何配置 - 使用
nuxt build命令并设置环境变量NITRO_PRESET=vercel-edge的 Vercel Edge Functions - 使用
nuxt build命令并设置环境变量NITRO_PRESET=netlify-edge的 Netlify Edge Functions
注意,使用边缘端渲染并配合路由规则时可以使用 混合渲染。
你可以探索部署在上述部分平台上的开源示例: