🎨 渲染模式演进史

一句话定性

“HTML 应该在哪里生成?” 这个看似简单的问题,前端用了二十年来回横跳。服务端 → 客户端 → 又回服务端 → 预生成 → 增量 → 组件级混合——这不是绕圈,而是一条否定之否定的螺旋:每一次”回归”都站在更高的维度上,带着上一轮学到的东西。


〇、一张图看懂整条螺旋

 渲染在哪?  服务端 ◄──────────────────────────────► 客户端

 MPA   ●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━●  整页在服务端,但整页刷新
        │ 痛点:每次跳转白屏、体验割裂
        ▼
 SPA           ●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━●  全搬到客户端,体验丝滑
        │ 痛点:首屏白屏、SEO 灾难、JS 巨大
        ▼
 SSR   ●━━━━━━━━━━━━━━━━━━━━━━━●  首屏回服务端渲染,再"注水"成 SPA
        │ 痛点:服务器压力、TTFB 慢、hydration 开销
        ▼
 SSG   ●━━●  构建时把页面全预渲染成静态 HTML(最快、最便宜)
        │ 痛点:内容一变就要全站重建,不适合动态/海量页面
        ▼
 ISR        ●━━●  静态为主 + 按需/定时在后台再生单页(SSG 与 SSR 的折中)
        │ 痛点:"陈旧窗口"、缓存失效心智复杂
        ▼
 RSC   ●━━━━━━━━━━━━━━┅┅┅┅●  组件级混合:每个组件自己决定在服务端还是客户端渲染
                            (维度从"页面"下降到"组件",螺旋上升的终点)

看懂这张图的关键

钟摆不是在”服务端 vs 客户端”之间来回荡——它的摆幅在收窄,粒度在变细:从”整个应用选一种”,到”每个页面选一种”,最后到”每个组件选一种”。这就是螺旋,不是圆圈。


一、MPA —— 起点:服务端渲染,但整页刷新

MPA(Multi-Page Application,多页应用) 是 Web 的原始形态(PHP / JSP / Rails / Django 时代,对应 1995-2005 浏览器时代2005-2013 Ajax时代早期)。

  • 怎么工作:每次点链接,浏览器向服务器要一个完整的 HTML 页面,服务器拼好返回,浏览器整页重绘。
  • 优点:首屏快(HTML 直接到手)、SEO 完美(爬虫拿到的就是完整内容)、心智简单。
  • 副作用 / 催生下一代:

    整页刷新 → 白屏闪烁、状态丢失、体验割裂。在"网页要像桌面应用一样流畅"的诉求下,这成了不可接受的硬伤。这个痛点直接催生了 SPA。

    每次跳转都


二、SPA —— 第一次否定:全搬到客户端,丝滑但失明

SPA(Single-Page Application,单页应用)2013-2018 SPA时代的主角,React / Vue / AngularJS 把它推向巅峰。

  • 怎么工作:服务器只返回一个近乎空的 HTML 壳(<div id="root"></div>)+ 一大坨 JS。JS 在浏览器里跑起来,在客户端动态生成所有 DOM,路由切换也由 JS 接管,不再请求新页面。
  • 解决了什么:页面切换无刷新、体验如桌面应用、UI = f(state) 让复杂交互可维护(详见 2013-2018 SPA时代)。
  • 副作用 / 催生下一代:

    SPA 的三宗罪

    1. 首屏白屏(慢):用户要先下载 → 解析 → 执行一大坨 JS,才能看到内容。弱网/低端机上灾难。
    2. SEO 失明:搜索引擎爬虫看到的是空 <div>,内容全靠 JS 生成,抓不到。对内容站是致命的。
    3. JS 体积爆炸:框架 + 业务 + 依赖全打进 bundle。

    白屏 + SEO 这两条,直接逼着前端”回到服务端”——SSR 回归。


三、SSR —— 第二次否定(向 MPA 致敬):服务端渲染回归,但带着 SPA 的灵魂

SSR(Server-Side Rendering,服务端渲染) 不是退回 MPA,而是**“两全其美”的尝试**——2018-2023 工程化时代由 Next.js / Nuxt 把它平民化。

  • 怎么工作:
    1. 首屏:服务器把 React/Vue 组件渲染成完整 HTML 返回 → 用户立刻看到内容、爬虫也能抓到(解决 SPA 两宗罪)。
    2. 注水(Hydration):HTML 到达后,浏览器再下载 JS,给这堆”死 HTML” 绑定事件、接管路由 → 它又变回了一个活的 SPA
  • 解决了什么:首屏快 + SEO 好(像 MPA)+ 后续交互丝滑(像 SPA)。这是螺旋上升的典型——用更高维度的方案,同时拿到了 MPA 和 SPA 的优点
  • 副作用 / 催生下一代:

    Warning

    1. 服务器压力:每个请求都要在服务器实时跑一遍渲染,CPU 成本高,扛不住高流量。
    2. TTFB 变慢:服务器要等数据、跑渲染,首字节时间(Time To First Byte)被拉长。
    3. Hydration 开销:HTML 已经显示,但在 JS 注水完成前不可交互(出现”看得见点不动”的尴尬期),而且注水要把整棵树的 JS 再下载+执行一遍,等于渲染了两次

    “既然很多页面内容不常变,为什么每次请求都要重新渲染?”——这个反问催生了 SSG。


四、SSG —— 把渲染提前到”构建时”:最快最便宜,但只适合静态

SSG(Static Site Generation,静态站点生成) 把 SSR 的渲染时机从”每次请求时”提前到”构建时”(Gatsby、Next.js getStaticProps、Nuxt generate、SvelteKit 等)。

  • 怎么工作:build 阶段就把所有页面预渲染成纯静态 HTML 文件,部署到 CDN。用户请求时直接拿现成的 HTML,服务器不需要做任何计算。
  • 解决了什么:
    • 最快:CDN 边缘节点直出静态文件,TTFB 极低。
    • 最便宜 + 最稳:没有运行时服务器渲染,无 CPU 压力,抗住任意流量,安全面也小。
    • SEO 完美(静态 HTML)。
  • 副作用 / 催生下一代:

    Warning

    1. 不适合动态内容:内容一变,理论上要重新构建整站
    2. 海量页面构建爆炸:一个有 10 万商品的电商,构建一次要渲染 10 万页,耗时以小时计。
    3. 实时性差:改个价格要等下一次部署才生效。

    矛盾很尖锐:SSG 又快又便宜,但”全量重建”让它无法应对动态/海量场景。能不能”大部分静态 + 只更新变化的那几页”?——ISR 应运而生。


五、ISR —— 折中的艺术:增量静态再生

ISR(Incremental Static Regeneration,增量静态再生) 是 Next.js 在 2018-2023 工程化时代提出的关键创新,SSG 与 SSR 之间的折中

  • 怎么工作:页面先用 SSG 的静态版本服务(快、便宜),但给它设一个”保鲜期”。过期后第一个访问的用户仍拿到旧版本(不阻塞),服务器在后台悄悄重新生成这一页,生成好后替换缓存——后续用户就看到新版本了。也支持按需触发(on-demand)单页再生。
    请求 ──► 有静态缓存?──是──► 直接返回(快)
                         └─未过期 → 返回
                         └─已过期 → 仍返回旧的 + 后台静默再生该页 ──► 替换缓存
    
  • 解决了什么:
    • 拿到 SSG 的速度和成本(绝大多数请求命中静态缓存)。
    • 又拿到 SSR 的新鲜度(内容能更新),且只再生变化的那一页,不用全站重建。
    • 海量页面也能用(按需生成,不必构建时全量预渲染)。
  • 副作用:

    Warning

    1. “陈旧窗口”:过期后到再生完成前,部分用户会看到旧内容,最终一致而非强一致
    2. 缓存失效心智复杂:revalidate 时间、按需失效、CDN 缓存层叠加,调试和推理成本上升。

螺旋的小结

到 ISR 为止,前端已经能在**“页面”这个粒度**上自由选择渲染策略:静态的页用 SSG、动态首屏用 SSR、半动态用 ISR。但选择仍然是”整个页面选一种”。下一步的飞跃,是把粒度从”页面”降到”组件”。


六、RSC —— 第三次否定(螺旋的当前终点):组件级的服务端/客户端混合

RSC(React Server Components,React 服务端组件)React 18+ 引入的新范式,把渲染选择的粒度从”页面”细化到”组件”

  • 怎么工作:在一棵组件树里,每个组件可以是:
    • Server Component(默认):只在服务端渲染,可以直接读数据库/文件、用大依赖,它的 JS 永远不会发到浏览器(零客户端体积)。
    • Client Component('use client'):需要交互(useState/事件)的组件,在客户端运行。
    • 两者在同一棵树里交错嵌套,服务端渲染出的结果以特殊格式流式传给客户端,客户端只为需要交互的部分注水。
    <Page>                    ← Server(读数据库,0 JS 到客户端)
      <Article>               ← Server(纯展示,0 JS)
      <LikeButton 'use client'/>  ← Client(要交互,这部分才下发 JS)
    </Page>
    
  • 解决了什么(同时回答了前面所有痛点):
    • 击穿 SPA 的 JS 体积问题:展示型组件零客户端 JS,bundle 只剩真正需要交互的部分。
    • 缓解 SSR 的 hydration 开销:只注水 Client Component,不再”整棵树渲染两次”。
    • 数据获取下沉到组件:组件直接在服务端拿数据,告别 props 层层透传和 waterfall。
  • 副作用 / 当前争议:

    Warning

    1. 心智模型陡增:“这个组件到底在哪跑?能不能用 useState?能不能 import 那个客户端库?”——服务端/客户端的边界成了新的认知负担(见 2018-2023 工程化时代副作用)。
    2. 生态适配成本:大量库要区分 server/client 才能用,迁移有摩擦。
    3. 强绑定元框架:RSC 几乎只能在 Next.js 等元框架里用,加深 lock-in。

七、元框架:让所有这些模式”开箱即用”

单凭框架(React/Vue/Svelte)只能渲染组件;把路由、数据获取、SSR/SSG/ISR/RSC、构建优化全部封装好的,是元框架(meta-framework)。它们是渲染模式螺旋能落地的真正载体:

元框架底层框架角色
Next.jsReact模式最全(SSR/SSG/ISR/RSC 全都它先做),Vercel 出品,事实标准
NuxtVueVue 生态的 Next,SSR/SSG/混合渲染
SvelteKitSvelte编译时 + 多渲染模式,产物极小
Remix / Astro 等多种Remix 强调 Web 标准与嵌套路由;Astro 主打”默认零 JS”的内容站

关键洞察

元框架的出现,意味着 MPA/SPA/SSR/SSG/ISR/RSC 不再是单选题,而是一个工具箱——同一个应用里,营销页用 SSG、商品页用 ISR、后台用 SPA、首页用 RSC,按页面、按组件混搭。渲染模式之争的终局不是”谁赢”,而是”全都要,按需取用”。


八、全景因果链

 MPA(服务端渲染,整页刷新)
   │ 整页刷新、体验割裂
   ▼
 SPA(客户端渲染,体验丝滑)            ◄── [[React]]/[[Vue]] @ [[2013-2018 SPA时代]]
   │ 首屏白屏 + SEO 失明 + JS 爆炸
   ▼
 SSR(服务端渲染回归 + 注水成 SPA)     ◄── Next.js/Nuxt @ [[2018-2023 工程化时代]]
   │ 服务器压力 + TTFB 慢 + hydration 双重渲染
   ▼
 SSG(构建时预渲染,最快最便宜)
   │ 内容一变要全站重建,不适合动态/海量
   ▼
 ISR(静态为主 + 按需后台再生,折中)
   │ 陈旧窗口 + 缓存心智复杂
   ▼
 RSC(粒度从"页面"降到"组件",服务端/客户端混合) ◄── [[React]] 18+
   │
   ▼
 元框架把以上全部变成"工具箱",按页面/组件自由混搭

历史地位

渲染模式演进史是前端最典型的**“否定之否定”螺旋**:答案在服务端和客户端之间来回摆动,但每一次回归都站在更高的维度——MPA 是”整页”,SSR 是”页面级两全”,RSC 是”组件级混合”。它的本质是一场永恒的权衡:首屏速度 vs 交互体验 vs SEO vs 服务器成本 vs 开发心智,没有银弹,只有针对场景的最优解。理解这条螺旋,就理解了现代前端架构决策的全部张力。


🔗 相关:React | Vue | Svelte | 2013-2018 SPA时代 | 2018-2023 工程化时代 | 1995-2005 浏览器时代 | 2005-2013 Ajax时代