--- 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 ``` Nuxt 中服务端取数(结果会序列化到页面中,客户端水合时不会再请求一次): ```vue ``` Next.js 的写法见下一课和 [examples/next-ai-chat](examples/next-ai-chat/)。 ## 易错点 - 在服务端渲染阶段访问 `window`、`localStorage`。 - 服务端渲染时使用了全局单例状态(Pinia、模块级变量),导致不同用户的数据串在一起:每个请求要创建新的实例。 - 把带用户信息的 SSR 页面缓存到 CDN。 - SSR 页面里调用很慢的接口,拖慢整个首屏:并行取数、流式渲染、超时降级。 - 以为用了 SSR 就一定更快:服务器离用户远、取数慢时,TTFB 反而变差。 ## 面试常问 - CSR、SSR、SSG、ISR 的区别和适用场景? - 什么是水合?水合不匹配的原因? - SSR 项目有哪些常见的坑?