Files
zhuangang/topics/frontend/ssr/01-rendering-modes.md
T

82 lines
3.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: frontend.ssr.rendering-modes
title: 渲染模式:CSR / SSR / SSG / ISR、水合、流式渲染和 Nuxt 基础
type: lesson
domain: frontend
stack: [ssr]
tags: [CSR, SSR, SSG, ISR, 水合, Hydration, 流式渲染, Nuxt, SEO]
level: intermediate
prerequisites: [frontend.vue.reactivity-composition]
est_hours: 2.5
related: [frontend.ssr.nextjs-app-router, frontend.fe-testing-perf.index, frontend.ssr.interview]
week: [7]
order: 1
---
# 渲染模式:CSR / SSR / SSG / ISR、水合、流式渲染和 Nuxt 基础
## 解释说明
| 模式 | 在哪里生成 HTML | 优点 | 缺点 | 适合 |
| --- | --- | --- | --- | --- |
| CSR(客户端渲染) | 浏览器执行 JS 后生成 | 服务器简单,交互流畅 | 首屏慢、SEO 差 | 后台系统、登录后的应用 |
| SSR(服务端渲染) | 每次请求时服务器生成 | 首屏快、SEO 好、数据实时 | 服务器压力、需要 Node 服务 | 内容和数据经常变化的公开页面 |
| SSG(静态生成) | 构建时生成 | 最快、可放 CDN | 数据更新要重新构建 | 文档、官网、博客 |
| ISR(增量静态再生成) | 构建时生成 + 过期后后台重新生成 | 兼顾速度和更新 | 有短暂的旧数据 | 商品页、文章页 |
**水合(Hydration)**:服务端返回 HTML 后,浏览器加载 JS,把事件监听和状态“接”到已有的 DOM 上,页面才可以交互。问题:
- 水合不匹配:服务端和客户端渲染结果不一致(用了 `Date.now()`、`Math.random()`、`window`、本地时区),会报警告甚至重新渲染。
- 首屏能看到但点不动(JS 还没加载完):通过减少客户端 JS、部分水合 / 服务端组件、懒加载缓解。
**流式渲染**:服务器先发送页面框架,慢的数据块准备好后再流式补上(React 的 Suspense、Next.js 的 `loading.tsx`),首字节更快。
**Nuxt**(Vue 的全栈框架,当前大版本 Nuxt 4,以官方文档为准:https://nuxt.com/docs):基于文件的路由、`useFetch` / `useAsyncData` 在服务端取数并把结果传给客户端避免重复请求、`server/` 目录写接口(Nitro 引擎)、按路由配置渲染模式(`routeRules`:SSR、预渲染、ISR 类缓存)。
## 案例
电商的帮助中心:常见问题页需要 SEO 和最快的首屏,用 SSG / ISR;智能客服对话需要登录和实时交互,是客户端组件;订单页登录后才能看,用 CSR 或 SSR 都可以,但要注意不能把用户数据缓存到 CDN。
## 代码示例
避免水合不匹配:只在客户端执行的逻辑放到挂载之后。
```vue
<script setup lang="ts">
import { onMounted, ref } from "vue";
const now = ref<string | null>(null); // 服务端渲染时为 null,两端一致
onMounted(() => {
now.value = new Date().toLocaleString("zh-CN"); // 只在浏览器执行
});
</script>
<template>
<span>{{ now ?? "加载中" }}</span>
</template>
```
Nuxt 中服务端取数(结果会序列化到页面中,客户端水合时不会再请求一次):
```vue
<script setup lang="ts">
const { data: faqs, error } = await useFetch("/api/faqs");
</script>
```
Next.js 的写法见下一课和 [examples/next-ai-chat](examples/next-ai-chat/)。
## 易错点
- 在服务端渲染阶段访问 `window`、`localStorage`。
- 服务端渲染时使用了全局单例状态(Pinia、模块级变量),导致不同用户的数据串在一起:每个请求要创建新的实例。
- 把带用户信息的 SSR 页面缓存到 CDN。
- SSR 页面里调用很慢的接口,拖慢整个首屏:并行取数、流式渲染、超时降级。
- 以为用了 SSR 就一定更快:服务器离用户远、取数慢时,TTFB 反而变差。
## 面试常问
- CSR、SSR、SSG、ISR 的区别和适用场景?
- 什么是水合?水合不匹配的原因?
- SSR 项目有哪些常见的坑?