CSR和SSR

CSR(Client-Side Rendering): 客户端渲染

服务器主要负责提供静态的HTML文件(可能包含一些基本的HTML结构和JavaScript脚本),而真正的页面渲染工作则完全由客户端的浏览器来完成。这意味着页面内容是在用户的浏览器上动态生成的。

优点:

响应速度快:一旦HTML文件加载完成,浏览器就可以开始渲染页面,而不需要等待服务器返回完整的渲染结果。 动态性强:由于页面渲染在客户端进行,因此可以方便地实现各种动态交互效果。 前端部署简单:只需要一个静态服务即可部署前端代码,降低了部署成本。

缺点:

首屏加载时间长:由于需要加载整个JavaScript包,可能导致首屏加载时间较长,特别是对于复杂的单页应用(SPA)。 不利于SEO:搜索引擎爬虫可能无法很好地解析由JavaScript动态生成的页面内容,导致SEO效果较差。 白屏时间:在JavaScript代码加载和执行期间,用户可能会看到空白的页面,即所谓的“白屏时间”。

SSR(Server-Side Rendering): 服务端渲染

SSR 的流程一定要熟:构建 → 请求 → 服务端渲染 → 注水 → 客户端接管

  • SSR 是一种在服务器端完成页面渲染的技术。
  • 在SSR下,服务器端接收到客户端的请求后,会根据 客户端 请求的数据 以及 模板文件 生成完整的HTML页面。然后将这个完整的HTML页面直接发送给客户端。
  • 这样,用户可以直接看到完成的内容,无需等待JavaScript加载和执行。

SSR 模板文件

👉 SSR 本质上是生成一段 HTML 字符串, 然后插入到 SSR 模板文件 中。

模板文件: SSR 模板 = 静态 HTML 容器 (纯HTML字符串) + 插槽占位

  • SSR 模板文件 不会渲染,而是“接收渲染结果”的容器。

优点: 使用SSR模板文件的好处是无需每次构建整个页面,只需要每次将数据插入到slot中,从而渲染插槽即可。

  • SSR模板文件 不会被服务端构建器编译(build/compile)
    • 构建器(如 Vite、Webpack、Rollup)并不会处理这个 index.html 模板(它可能会被拷贝,但不会编译、分析依赖、生成 bundle)。
  • SSR模板文件 不会被客户端解析模板语法而渲染(render)
    • SSR模板文件 不会被 像 Vue/React/San 这样的框架渲染,因为浏览器只会看到它已经被替换好的 HTML,根本不会看到插槽占位
  • SSR模版只是 在 node 读取模版时,使用JS 将 插槽占位 替换成 服务端解析客户端获得的数据

与slot的区别:

  • SSR 插槽 = 等待“某个组件渲染结果”的静态 HTML 占位符
  • 组件插槽 = 等待“其他组件传入内容”的动态子组件插入点

SSR 反解 —— Hydration(注水)机制

  • 🌟 既然SSR是在服务端渲染一个HTML的过程,那么客户端是如何接管运行的呢?如何执行页面上的JS逻辑呢?

  • 这就涉及到 SSR反解,即挂载JS的过程。 又称 Hydration(注水)。

  • SSR 注水时的 JS 逻辑,是浏览器从 HTML 中加载 script 脚本文件后执行的,而注水的入口逻辑 就是 JS 应用接管 DOM 的起点。

SSR 的“反解”就是客户端的 Hydration(注水)过程,即将 SSR 渲染好的 HTML,挂上 JS 逻辑,变成真正可交互的应用。

🔥 注水机制的 本质 是在 已有DOM上挂载JS逻辑

❌ 这里需要区分CSR。 Hydration 是 SSR 独有的步骤,纯 CSR 是 没有 Hydration过程的,因为CSR 不会预先接管HTML,而是自己从 JS 渲染 DOM。

  • React 的 hydrateRoot()、Vue 的 app.mount()、san的 myComponent.attach(el, { ssr: true }) 都是注水入口。

  • 但是 有的时候 我们也会在 CSR 项目里看到下面这种代码:

    const app = createApp(App); 
    app.mount('#app');
    
  • 但这其实不是注水,这里 #app 是空的,App 是从 0 渲染出来的。只是 API 长得像 SSR 的注水 mount(),但它做的是“创建 DOM”,不是“复用已有 DOM”。

  • SSR 的 HTML 是“服务器生成的”,DOM是在服务端生成的,Hydration 是“接管”, 只需要在 DOM上挂载,而无需重新创建DOM;

  • 在CSR中,服务器返回一份 空的 HTML, CSR 的 HTML 是“客户端生成的”, 需要 直接从 0 开始创建DOM,没有可接管的内容。

注水的作用

  • 注水不是重新渲染,而是对比已有 HTML 和虚拟 DOM,找到匹配点, 然后 “在已有 DOM 上挂载 JS 逻辑” ;这样可以省掉客户端从 0 渲染,页面加载速度更快。

注水顺序:

  1. 【初始化客户端数据】 创建响应式状态 或 store 状态 【还没有对比DOM结构】

    • 执行组件的 initData() 或 data: { … }
    • 恢复 Vue 的 data(),React 的 useState()、Redux 状态等
    • 或读取 window.INITIAL_DATA 来还原服务端数据
  2. 【对比DOM】

    • 对比 客户端 JS 渲染出来的“虚拟 DOM” 和 页面中已经由服务端生成并送到客户端的“真实 DOM” (不一致可能会触发警告或 DOM patch)
    • 如果不一致,框架会:
      • 报 warning(如 Vue 的 hydration mismatch)
      • 或直接 patch DOM(丢掉 SSR 性能)
    • 🔁 这是为了让“JS 状态”与“页面结构”达成一致。
    • ⚠️ 特别注意:不是所有属性都“必须对比”
      • 一些如 data-* 属性、非交互性属性,或者某些 aria-* 辅助属性,框架可能会有容忍度。
      • 但关键属性(如:id、class、key、绑定属性、样式、children 顺序)都必须对比且一致,否则注水可能失败。
  3. 【挂载事件监听器】

SSR的优缺点

  • 优点:

    • 首屏加载速度快:由于服务器已经生成了完整的HTML页面,因此客户端可以直接显示这个页面,无需等待JavaScript加载和执行。
    • SEO友好:搜索引擎爬虫可以很好地解析由服务器生成的HTML页面内容,有利于SEO优化。
    • 适合复杂页面:对于包含大量数据、需要复杂计算的页面,SSR可以更好地处理并减少客户端的负载。
  • 缺点:

    • 服务器压力大:对于每个请求,服务器都需要重新渲染页面,这可能导致服务器压力过大。
    • 开发限制:SSR要求开发者在编写Vue组件时,需要考虑到服务器端和客户端环境的差异,不能过度依赖客户端环境。
    • 调试困难:SSR的调试过程相对复杂,需要同时考虑到服务器端和客户端的日志和错误信息。

🚧 Diff 注水 vs 挂载 vs 重新渲染

类型动作会创建 DOM?会挂载事件?用在哪
SSR服务器渲染HTML首屏
Hydration注水接管 HTML首屏
CSR 挂载浏览器全量渲染二次跳转、非首屏