---
title: "渲染模式"
description: "了解 Nuxt 中可用的不同渲染模式。"
canonical_url: "https://nuxt.zhcndoc.com/docs/4.x/guide/concepts/rendering"
---
# 渲染模式

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

Nuxt 支持不同的渲染模式，包括[通用渲染](https://nuxt.zhcndoc.com/docs/4.x/guide/concepts/rendering#universal-rendering)、[客户端渲染](https://nuxt.zhcndoc.com/docs/4.x/guide/concepts/rendering#client-side-rendering)，还支持[混合渲染](https://nuxt.zhcndoc.com/docs/4.x/guide/concepts/rendering#hybrid-rendering)，并可以在 [CDN 边缘服务器](https://nuxt.zhcndoc.com/docs/4.x/guide/concepts/rendering#edge-side-rendering)上渲染你的应用。

浏览器和服务器都可以解释 JavaScript 代码，将 Vue.js 组件转换为 HTML 元素。这个步骤称为“渲染”。Nuxt 同时支持 **通用渲染** 和 **客户端渲染**。这两种方法各有优缺点，下面将介绍。

默认情况下，Nuxt 使用 **通用渲染** 来提供更好的用户体验和性能，并优化搜索引擎索引，但你可以通过[一行配置](https://nuxt.zhcndoc.com/docs/4.x/api/nuxt-config#ssr)切换渲染模式。

## 通用渲染

这一步类似于传统由 PHP 或 Ruby 应用执行的 **服务器端渲染**。当浏览器请求某个 URL 并启用了通用渲染时，Nuxt 会在服务器环境中运行 JavaScript（Vue.js）代码，并返回一个完全渲染好的 HTML 页面到浏览器。如果页面是预先生成的，Nuxt 也可能从缓存返回一个完全渲染好的 HTML 页面。用户会立即获得应用初始内容的全部信息，这与客户端渲染不同。

当 HTML 文档被下载后，浏览器会解析它，Vue.js 会接管该文档。曾经在服务器上运行的相同 JavaScript 代码现在会在客户端（浏览器）中“再次”运行，从而通过将监听器绑定到 HTML 来启用交互（因此称为 **通用渲染**）。这称为 **水合（Hydration）**。当水合完成后，页面即可享受动态界面和页面切换等好处。

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

![用户在 HTML 文档加载时可以访问静态内容。随后水合允许页面交互性](https://nuxt.zhcndoc.com/assets/docs/concepts/rendering/ssr.svg)

**什么是服务器渲染，什么是客户端渲染？**

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

```vue [app/app.vue]
<script setup lang="ts">
const counter = ref(0) // 在服务器和客户端环境中执行

const handleClick = () => {
  counter.value++ // 仅在客户端环境中执行
}
</script>

<template>
  <div>
    <p>计数：{{ counter }}</p>
    <button @click="handleClick">
      增加
    </button>
  </div>
</template>
```

在初始请求时，由于 `counter` 被渲染在 `<p>` 标签内部，因此它会在服务器上初始化。`handleClick` 的内容在此处不会被执行。在浏览器进行水合时，`counter` ref 会被重新初始化。`handleClick` 最终会绑定到按钮上；因此可以合理地推断 `handleClick` 的主体将始终在浏览器环境中运行。

[中间件](https://nuxt.zhcndoc.com/docs/4.x/directory-structure/app/middleware)和[页面](https://nuxt.zhcndoc.com/docs/4.x/directory-structure/app/pages)会在服务器上运行，也会在客户端水合期间运行。[插件](https://nuxt.zhcndoc.com/docs/4.x/directory-structure/app/plugins)可以在服务器端、客户端或两端渲染。[组件](https://nuxt.zhcndoc.com/docs/4.x/directory-structure/app/components)也可以强制仅在客户端运行。[组合式函数](https://nuxt.zhcndoc.com/docs/4.x/directory-structure/app/composables)和[工具函数](https://nuxt.zhcndoc.com/docs/4.x/directory-structure/app/utils)则根据其使用上下文进行渲染。

**服务器端渲染的好处：**

- **性能**：用户可以立即访问页面内容，因为浏览器显示静态内容要比显示由 JavaScript 生成的内容更快。与此同时，Nuxt 在水合过程中保留了 Web 应用的交互性。
- **搜索引擎优化**：通用渲染将页面的完整 HTML 内容像传统服务器应用一样交付给浏览器。网络爬虫可以直接索引页面内容，这使得通用渲染成为希望快速被索引内容的优秀选择。

**服务器端渲染的缺点：**

- **开发限制**：服务器和浏览器环境不提供相同的 API，要编写能在两端无缝运行的代码可能会比较棘手。幸运的是，Nuxt 提供了指南和特定变量，帮助你确定代码在哪个环境中执行。
- **成本**：要动态渲染页面，服务器必须保持运行。这会像任何传统服务器一样产生每月费用。不过，由于通用渲染中客户端导航由浏览器接管，服务器调用会大幅减少。利用[边缘端渲染](https://nuxt.zhcndoc.com/docs/4.x/guide/concepts/rendering#edge-side-rendering)可以降低成本。

通用渲染非常灵活，几乎适用于任何用例，尤其适合面向内容的网站：**博客、营销网站、作品集、电子商务网站和市场平台。**

::tip
关于编写不会导致水合不匹配（hydration mismatch）的 Vue 代码的更多示例，请参阅 [Vue 官方文档](https://vue.zhcndoc.com/guide/scaling-up/ssr#hydration-mismatch)。
::

::important
当导入依赖浏览器 API 且有副作用的库时，请确保导入它的组件仅在客户端被调用。打包器不会对包含副作用的模块的导入进行 tree-shake。
::

## 客户端渲染

传统的 Vue.js 应用默认是在浏览器（或“客户端”）中渲染的。然后，Vue.js 在浏览器下载并解析包含创建当前界面的指令的所有 JavaScript 代码后生成 HTML 元素。

![用户必须等待浏览器下载、解析并执行 JavaScript 才能看到页面内容](https://nuxt.zhcndoc.com/assets/docs/concepts/rendering/csr.svg)

**客户端渲染的好处：**

- **开发速度**：在完全客户端上开发时，我们不必担心代码的服务器兼容性，例如使用仅属于浏览器的 API（如 `window` 对象）。
- **成本更低**：运行服务器会增加基础设施成本，因为你需要在支持 JavaScript 的平台上运行。我们可以在任何静态服务器上托管仅客户端应用，提供 HTML、CSS 和 JavaScript 文件即可。
- **离线**：由于代码全部在浏览器中运行，当网络不可用时它仍能良好工作。

**客户端渲染的缺点：**

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

客户端渲染适合高度交互的 **Web 应用**，这些应用不需要被索引或用户访问频繁。它可以利用浏览器缓存在后续访问中跳过下载阶段，例如 **SaaS、后台管理应用或在线游戏**。

你可以在 `nuxt.config.ts` 中启用仅客户端渲染：

```ts [nuxt.config.ts]
export default defineNuxtConfig({
  ssr: false,
})
```

::note
如果你使用 `ssr: false`，还应在 `app/spa-loading-template.html` 中放置一个 HTML 文件，其中包含你希望用于渲染加载屏幕的 HTML。在应用准备就绪之前，会显示该加载屏幕。
:read-more{title="SPA 加载模板" to="https://nuxt.zhcndoc.com/docs/4.x/directory-structure/app/spa-loading-template"}
::

::video-accordion{title="观看 Alexander Lichter 关于使用 Nuxt 构建纯 SPA 的视频" videoId="7Lr0QTP1Ro8"}
::

### 部署静态客户端渲染应用

如果你使用 `nuxt generate` 或 `nuxt build --prerender` 命令将应用部署到[静态托管](https://nuxt.zhcndoc.com/docs/4.x/getting-started/deployment#static-hosting)，那么默认情况下，Nuxt 会将每个页面渲染为单独的静态 HTML 文件。

::warning
如果你使用 `nuxt generate` 或 `nuxt build --prerender` 对应用进行预渲染，那么输出文件夹中将不会包含任何服务器，因此你将无法使用任何服务器端点。如果你需要服务器功能，请改用 `nuxt build`。
::

如果你完全使用客户端渲染，那么这可能是不必要的。你可能只需要一个 `index.html` 文件，以及 `200.html` 和 `404.html` 回退文件，并可以让你的静态网页托管服务为所有请求返回这些文件。

为了实现这一点，我们可以更改路由的预渲染方式。只需将以下内容添加到 `nuxt.config.ts` 中的[钩子](https://nuxt.zhcndoc.com/docs/4.x/api/advanced/hooks#nuxt-hooks-build-time)：

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

这将生成三个文件：

- `index.html`
- `200.html`
- `404.html`

#### 200.html 和 404.html 是什么？

静态托管服务需要一个用于客户端路由和缺失路径的 HTML 壳层。Nuxt 为此会输出两个 SPA 回退文件：

- **`200.html`**：当路径未命中时，如果你希望客户端路由器来处理 URL，就提供这个文件。
- **`404.html`**：当托管服务需要保持 404 状态但仍然加载你的应用时，就提供这个文件。

`nuxt generate` 和 `nuxt build --prerender` 会将它们写入 `.output/public/`。仅执行不带预渲染的 `nuxt build` 不会生成这些文件。如果使用混合路由规则，请通过路由规则添加这些回退文件，或者在需要它们时运行预渲染构建。将你的托管服务指向提供商所期望的那个文件。

#### 服务器端渲染错误页面

默认情况下，`404.html` 是一个空壳，因此你的 `error.vue`（及其布局和数据）只有在客户端应用启动后才会显示。你可以改为预渲染它：

```ts twoslash [nuxt.config.ts]
export default defineNuxtConfig({
  experimental: {
    prerenderErrorPages: true,
  },
})
```

Nuxt 随后会在构建时使用一个合成的 404 错误渲染 `error.vue`，并将结果写入 `404.html`。无论托管服务从哪个 URL 提供该文件，它都会在原位置完成水合。如果你的托管服务能够提供这些页面，请传入一个介于 400 和 599 之间的状态码数组（`[404, 500]`）来生成其他页面。

由于每个缺失路径都会提供同一个文件，错误页面无法依赖请求：`useRoute()`、`useRequestURL()` 和使用当前路径获取的数据会在预渲染 HTML 中保留构建时的值，并在水合时修正。将依赖请求的标记包裹在 [`<ClientOnly>`](https://nuxt.zhcndoc.com/docs/4.x/api/components/client-only) 中，并使用 `import.meta.prerender` 跳过依赖请求的数据获取：

```vue [error.vue]
<script setup lang="ts">
import type { NuxtError } from '#app'

defineProps<{ error: NuxtError }>()

const route = useRoute()

const { data: suggestions } = await useAsyncData(
  'error-suggestions',
  () => $fetch('/api/suggestions', { query: { path: route.path } }),
  // at build time the path is `/404.html`, so fetch on the client instead
  { server: !import.meta.prerender },
)
</script>

<template>
  <div>
    <h1>{{ error.statusCode }}</h1>
    <ClientOnly>
      <p>Nothing found at {{ route.path }}</p>
    </ClientOnly>
    <ul v-if="suggestions">
      <li
        v-for="suggestion in suggestions"
        :key="suggestion"
      >
        {{ suggestion }}
      </li>
    </ul>
  </div>
</template>
```

`import.meta.prerender` 只有在生成页面时才为 `true`，因此当应用以服务器模式部署时，同一个 `error.vue` 仍然会在服务器端渲染其数据。

#### 跳过客户端回退文件生成

预渲染客户端渲染应用时，Nuxt 默认会生成 `index.html`、`200.html` 和 `404.html` 文件。不过，如果你需要在构建时阻止生成其中任意或全部文件，可以使用 [Nitro](https://nuxt.zhcndoc.com/docs/4.x/getting-started/prerendering#runtime-prerender-configuration-prerendergenerate-nitro-hook) 的 `'prerender:generate'` 钩子。

```ts twoslash [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 缓存层](https://nitro.zhcndoc.com/guide/cache) 为路由封装缓存处理程序。

```ts twoslash [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 },
    // 重定向旧链接
    '/old-page': { redirect: '/new-page' },
    // 代理请求
    '/proxy/**': { proxy: 'https://api.example.com/**' },
  },
})
```

### 路由规则

可使用以下不同属性：

- `redirect: string`{lang="ts"} - 定义服务器端重定向。
- `proxy: string | object`{lang="ts"} - 使用字符串目标或包含 `to` 和 H3 代理选项的对象，将请求代理到另一个 URL。更多信息请参阅 [Nitro 路由规则文档](https://nitro.build/guide/routing#route-rules)。
- `ssr: boolean`{lang="ts"} - 禁用应用部分区域的 HTML 服务器端渲染，使其仅在浏览器中渲染（`ssr: false`）。
- `cors: boolean`{lang="ts"} - 使用 `cors: true` 自动添加 cors 标头；你可以通过覆盖 `headers` 自定义输出。
- `headers: object`{lang="ts"} - 为网站的特定区域添加标头，例如资源。
- `swr: number | boolean`{lang="ts"} - 为服务器响应添加缓存标头，并在服务器或反向代理上按可配置的 TTL（生存时间）缓存响应。Nitro 的 `node-server` 预设能够缓存完整响应。当 TTL 过期时，会先发送缓存的响应，同时在后台重新生成页面。如果使用 `true`，则会添加 `stale-while-revalidate` 标头，但不设置 MaxAge。
- `isr: number | boolean`{lang="ts"} - 行为与 `swr` 相同，但可以在支持此功能的平台（目前为 Netlify 或 Vercel）上将响应添加到 CDN 缓存中。如果使用 `true`，内容会在 CDN 中保留，直到下一次部署。
- `prerender: boolean`{lang="ts"} - 在构建时预渲染路由，并将其作为静态资源包含在构建产物中。
- `noScripts: boolean`{lang="ts"} - 禁用网站特定区域的 Nuxt 脚本和 JS 资源提示渲染。更多信息请参阅 [`noScripts`](https://nuxt.zhcndoc.com/docs/4.x/guide/going-further/features#features-noscripts)。
- `appMiddleware: string | string[] | Record<string, boolean>`{lang="ts"} - 允许你定义 Vue 应用部分中页面路径应运行或不应运行的中间件（即不包括 Nitro 路由）。

::note
使用 `isr` 或 `swr` 的路由也会在 HTML 旁生成 `_payload.json` 文件。客户端导航会加载这些缓存的载荷，而不是重新获取数据。更多信息请参阅[载荷提取](https://nuxt.zhcndoc.com/docs/4.x/getting-started/prerendering#payload-extraction)。
::

### 使用 `ssr: false` 时的服务器 Bundle 大小

受 `ssr: false` 覆盖的路由只会在浏览器中渲染，因此 Nuxt 会从服务器 Bundle 中排除其页面组件。只要覆盖每个可到达该页面的路径的规则都能在构建时解析，就会应用此规则，包括 `/products/**` 规则下的 `pages/products/[id].vue` 等动态路由。

当任何可到达该页面的路径仍可能在服务器上渲染时，页面会保留在服务器 Bundle 中：

- 更具体的规则会在仅客户端渲染的规则下方某处重新启用 SSR（`'/admin/**': { ssr: false }`{lang="ts"} 与 `'/admin/report': { ssr: true }`{lang="ts"}）
- 页面具有别名，或具有使用绝对路径声明的子页面，且该别名或子页面位于仅客户端渲染区域之外
- 页面是一个父级外壳，所渲染的子页面仍在服务器上渲染

这仅是一项构建时优化；它不会改变服务器发送到浏览器的内容。

在可能的情况下，路由规则会自动应用到部署平台的原生规则中，以获得最佳性能（目前支持 Netlify 和 Vercel）。

::important
请注意，使用 [`nuxt generate`](https://nuxt.zhcndoc.com/docs/4.x/api/commands/generate) 时不支持混合渲染。
::

**示例：**

::card-group
  :::card
  ```yaml [props]
  icon: i-simple-icons-github
  title: Nuxt Vercel ISR
  to: https://github.com/danielroe/nuxt-vercel-isr
  target: _blank
  ui.icon.base: text-black dark:text-white
  ```
  在 Vercel 上部署并使用混合渲染的 Nuxt 应用示例。
  :::
::

## 边缘端渲染

边缘端渲染（Edge-Side Rendering，ESR）是 Nuxt 引入的一项强大功能，它允许通过内容分发网络（CDN）的边缘服务器更接近用户地渲染你的 Nuxt 应用。通过利用 ESR，你可以确保性能提升和延迟降低，从而提供更好的用户体验。

使用 ESR 时，渲染过程被推送到网络的“边缘”——CDN 的边缘服务器。注意，ESR 更像是一个部署目标，而不是一种实际的渲染模式。

当发出页面请求时，请求不会一路到达原始服务器，而是在最近的边缘服务器被拦截。该服务器为页面生成 HTML 并将其发送回用户。此过程将数据需要传输的物理距离最小化，从而“降低延迟并更快加载页面”。

借助为 Nuxt 提供支持的[服务器引擎](https://nuxt.zhcndoc.com/docs/4.x/guide/concepts/server-engine) [Nitro](https://nitro.build/)，可以实现边缘端渲染。它支持跨平台运行，包括 Node.js、Deno、Cloudflare Workers 等。

目前可以利用 ESR 的平台有：

- [Cloudflare Pages](https://pages.cloudflare.com)，通过 git 集成和 `nuxt build` 命令实现零配置
- [Vercel Cloud](https://vercel.com/home)，使用 `nuxt build` 命令和 `NITRO_PRESET=vercel-edge` 环境变量
- [Netlify Edge Functions](https://www.netlify.com/platform/#netlify-edge-functions)，使用 `nuxt build` 命令和 `NITRO_PRESET=netlify-edge` 环境变量

请注意，**混合渲染**可以在使用带有路由规则的边缘端渲染时使用。


## Sitemap

See the full [sitemap](https://nuxt.zhcndoc.com/sitemap.md) for all pages.
