知

模块三 · 工程与体验

第 13 / 17 节19 分钟阅读型

SSR 与 Nuxt:首屏、水合,以及"该不该上"

SSR 不是性能银弹,它用服务端成本换首屏与 SEO。这一节讲清四种渲染模式的取舍、Nuxt 的数据约定、水合不一致的排查,以及什么时候不该上。

读完这一节,你能做到

  1. 说清 SSR 解决什么、代价是什么,而不是"首屏更快"。
  2. 在 CSR / SSR / SSG / ISR 之间做出有依据的选择。
  3. 用 Nuxt 的数据约定避免"服务端请求一次、客户端再来一次"。
  4. 定位 hydration mismatch,而不是靠 suppressHydrationWarning 盖住。

一、SSR 到底解决什么

三个真实收益

  1. 首屏内容更快可见:HTML 到达时已带内容,不需要等 JS 下载、执行、再请求接口。
  2. SEO 与分享卡:爬虫与社交平台拿到的是带内容的 HTML,而不是空壳。
  3. 弱网/低端设备体验:渲染在服务端做完,客户端只负责水合。

三个代价

代价具体表现
服务端成本每个请求都要跑一次渲染,QPS 高时比静态贵得多
复杂度生命周期、浏览器 API、第三方库都要考虑"服务端能不能跑"
缓存难做个性化内容不能直接缓存,公共内容才适合

一句话:SSR 是用"服务端算力"换"首屏与可索引性"。如果你的用户已经在用 App、或者页面在登录后才可见,这笔交易可能不划算。

二、四种渲染模式怎么选

模式内容生成时机适合不适合
CSR浏览器里后台系统、登录后页面营销页、需要 SEO 的公开页
SSR每次请求个性化 + 需要 SEO(商品详情、资讯)高 QPS 且内容不变的页面
SSG构建时文档、博客、营销落地页频繁变更的数据
ISR构建后按周期再生内容变化不频繁的列表与详情强实时数据

判断顺序

  1. 需要 SEO 吗?不需要 → CSR 就够,别引入复杂度。
  2. 内容对所有人一样吗?一样 → SSG 或 ISR。
  3. 内容因人而异且需要 SEO → SSR。

一个常见误判

后台管理系统上了 SSR:既没 SEO 收益,又多了一层服务端渲染成本。这类系统 CSR 更合适。

三、Nuxt 的关键约定

目录即结构

  • pages/ 下的文件路径就是路由,动态段用方括号。
  • server/api/ 下的文件就是后端接口,前端可以直接调。
  • composables/ 自动导入,不用手动 import。
  • middleware/ 放路由中间件,鉴权常放这里。

数据获取的两个入口

useAsyncDatauseFetch
本质通用异步数据封装基于 $fetch 的语法糖
适合任意异步逻辑(不只是 HTTP)直接调接口
关键能力服务端取到的数据会随页面传给客户端同左

为什么这件事重要

如果服务端取过数据、客户端又取一次,你就同时付了两次请求的代价,还可能出现"首屏是旧数据、水合后跳变"。用这两个 API 的目的之一就是把服务端结果传给客户端,避免二次请求。

$fetch 在两端的行为差异

同一段代码在服务端执行时是直接发请求,在客户端执行时是浏览器请求。所以不要在组件顶层直接 await $fetch,那会导致服务端与客户端各跑一次。

四、水合不一致怎么查

现象:控制台报 hydration mismatch,页面内容闪一下或事件失效。

五类常见原因

  1. 时间/随机数:渲染里用了当前时间、随机数,两端结果必然不同。
  2. 浏览器 API 判断:window、localStorage 在服务端不存在,条件渲染分支不一致。
  3. HTML 结构不合法:比如块级元素嵌在 <p> 里,浏览器会自动纠正,导致结构对不上。
  4. 第三方脚本改 DOM:广告、翻译插件在水合前改了节点。
  5. 数据不同步:服务端用了一份数据,客户端首帧用了另一份。

排查方法

  • 用"服务端渲染出的 HTML"和"客户端首帧 DOM"对比,找出第一个不同的节点。
  • 二分定位:先把可疑组件改成只在客户端渲染(<ClientOnly>),看问题是否消失。

什么时候 suppressHydrationWarning 才算正解

只在"这个差异是预期的"时用,比如显示相对时间(“3 分钟前”)这类服务端与客户端允许不同的文本。用它盖住结构性差异,等于把 bug 藏起来。

五、什么时候不该上 SSR

  • 页面在登录后才可见,且不需要 SEO。
  • 内部系统、后台、工具类页面。
  • 团队没有 Node 服务运维能力,只有静态托管。
  • 首屏瓶颈其实是接口慢,而不是渲染方式——换 SSR 不会让接口变快。

替代方案:静态预渲染 + 客户端数据请求,往往能用 20% 的复杂度拿到 80% 的收益。

六、常见坑

  1. 把 SSR 当性能银弹。接口慢的页面,SSR 只会让服务端也一起等。
  2. 组件顶层直接 await $fetch。两端各请求一次。
  3. 在渲染里用时间/随机数。必然水合不一致。
  4. 用 suppressHydrationWarning 掩盖结构差异。问题被藏起来,后续更难查。
  5. 个性化内容不做区分就上 CDN 缓存。会把 A 的内容发给 B。
  6. 第三方库不判断运行环境。服务端执行时报 window is not defined。

七、动手任务:自写 → AI 写 → 对比 → 取舍

四步不跳:① 自己写(限时,关掉补全)→ ② 让 AI 写同一题 → ③ 逐条比对 → ④ 说出保留谁的写法。 完整动作与记录模板见《AI 协作与代码评审》。

① 自己写

  1. 给一个"商品详情页"做渲染模式选型:写出四种模式的成本与收益,给出结论和理由。
  2. 写一段带数据获取的页面代码,要求服务端取一次、客户端不重复请求。

② 让 AI 写同一题

把第 2 题交给 AI,补一句 用 Nuxt 3 的约定,说明数据是怎么从服务端传到客户端的,不要用组件顶层 await。

③ 对比要盯的点

  • 它有没有在组件顶层写 await $fetch?
  • 有没有解释"数据随页面传输"这一层,还是只说"用 useFetch 就行"?
  • 选型部分是不是只说了"SSR 首屏快"?

④ 一句话取舍

它给的方案,在你的 QPS 下服务端扛得住吗?算一下再说。

八、自测

  1. SSR 的三个收益与三个代价分别是什么?
  2. CSR / SSR / SSG / ISR 各适合什么场景?
  3. useAsyncData 与 useFetch 的区别是什么?为什么重要?
  4. 为什么不能在组件顶层直接 await $fetch?
  5. 水合不一致的五类常见原因是什么?
  6. 什么情况下 suppressHydrationWarning 才算正解?

参考答案要点:首屏、SEO、弱网,服务端成本、复杂度、缓存;按 SEO、内容是否一致、是否个性化三步判断;前者通用异步、后者基于 $fetch,服务端结果可随页面传给客户端;两端各请求一次;时间随机、浏览器 API、结构不合法、第三方改 DOM、数据不同步;只在预期差异(相对时间)时用。

配套题目

★ 越多越该先背;点题目可看完整答案与深入版。

提示:读完可以顺手做几道对应题目,检验是否真的理解