Skip to content

前端渲染方案选型指南:从 CSR 到 PPR

摘要总结:针对前端渲染方案选型难题,系统解析 CSR、SSR、SSG、ISR、PPR 五种方案的核心原理、优缺点与适用场景,提出“构建时机选择 × 渲染位置选择”的分析框架。通过对比性能、实时性、SEO、架构复杂度四个维度,构建选型决策树,并以 VitePress 为例展示 SSG 的落地实践。总结出不同业务场景下的最优渲染策略,为前端架构决策提供参考。

1. 引言

一个 Web 网站(对应用也适用)从源代码到最终呈现在用户眼前,大致经历以下四个阶段:

提示

"构建 → 部署 → 请求 → 渲染",不同方案的本质差异 = 构建阶段的时机选择 × 渲染阶段的位置选择

1、构建:将源代码经过编译、转译等构建步骤,生成可部署的产物。

2、部署:将构建产物部署到服务器,使其可以通过网络访问。

3、请求:用户通过浏览器输入 URL 发起请求,服务器接收并处理该请求。

4、渲染:客户端(浏览器)接收服务器返回的内容,将其渲染为可视化页面。

上述流程中,"构建"和"渲染"两个阶段存在多种技术选择,不同的选择组合便构成了不同的渲染方案,这也是本文要讨论的核心问题——CSR、SSR、SSG 以及 ISR 四种方案各自的原理、适用场景以及如何选型。

2. CSR 渲染方案

CSR(Client-Side Rendering,客户端渲染)将整个渲染过程放在浏览器端完成,是单页应用(SPA)最典型的渲染方式。服务器只返回一个近乎空的 HTML 框架和一份 JavaScript Bundle,页面内容的生成与渲染完全由客户端 JS 驱动。

核心原理

服务端仅返回基础 HTML(无实际内容)和 JS Bundle,客户端完成 DOM 构建与渲染,例如 React 的 createRoot().render() 或 Vue 的 createApp().mount()

markdown
用户访问 URL

服务器返回:空 HTML 骨架(通常只有 <div id="app"></div>)

浏览器下载 JS Bundle 和 CSS

浏览器执行 JS,动态生成 DOM,渲染页面

页面呈现 & 可交互

优缺点

优点缺点
首屏之后交互流畅,适合复杂交互场景
前端独立开发,服务端只需提供数据接口(API)
首屏加载慢——用户必须等待 JS 下载、执行完成后才能看到内容
SEO 不友好——搜索引擎爬虫抓取到的 HTML 没有实际内容

适用与不适用场景

本质权衡:CSR 以"初始白屏"为代价,换取首屏之后的流畅交互体验。FCP(首次内容绘制)完全取决于 JS Bundle 的下载与执行速度。对于任何面向用户、首屏体验重要的场景,通常不是最优选择。

适用场景不适用场景
· 需要登录的后台管理系统(SEO 无关,首屏要求不高)
· 内部工具类应用
· 高交互富应用(如 Figma、Google Docs 等)
· 需要良好 SEO 的内容型网站(博客、官网、电商详情页等)
· 首屏体验重要的面向用户的产品

3. SSR 渲染方案

SSR(Server-Side Rendering,服务端渲染)在服务器接收到请求时实时执行页面代码、生成完整 HTML 并返回给浏览器,是与 CSR 相对的一种方案。服务器返回的 HTML 已经包含页面内容,浏览器可以直接呈现,随后进行"水合"(Hydration)使页面可交互。React 的 renderToString()、Vue 的 renderToString() 都是典型的服务端渲染 API。

代表框架:Next.js、Nuxt.js、SvelteKit。

核心原理

服务器在接收到请求时,实时执行页面组件代码,将渲染结果(包含数据的完整 HTML)返回给浏览器。浏览器直接显示内容,无需等待 JS 执行;随后加载 JS 并完成"水合",页面才真正具备交互能力。

markdown
用户访问 URL

服务器接收请求,执行 Vue/React 组件(如 renderToString())

生成完整带内容的 HTML,立即返回给浏览器

浏览器直接显示页面内容(首屏极快)

JS 加载完成后"水合"(Hydration),页面可交互

优缺点

优点缺点
首屏快——服务器返回的 HTML 已包含完整内容,浏览器可直接呈现
SEO 友好——搜索引擎爬虫能抓取到完整内容
服务器压力大——每个请求都需要服务器实时渲染,增加计算开销
TTI 可能延迟——页面虽已可见,但需等待 JS 水合完成后才可交互

适用与不适用场景

本质权衡:SSR 以服务器实时渲染为代价,换取首屏速度和 SEO 效果。水合过程完成后用户体验与 CSR 无异,但水合本身也是一次额外的性能开销,需注意避免"水合延迟"问题。

适用场景不适用场景
· 内容型网站(博客、新闻站、文档站)
· 需要良好 SEO 的营销落地页
· 需要首屏快速呈现的面向用户的产品
· 高度交互的富应用(后台系统、数据可视化平台)
· 请求量极大、服务器成本敏感的高流量场景
· 强依赖客户端个性化数据的页面

4. SSG 渲染方案

SSG(Static Site Generation,静态站点生成)在构建阶段提前执行页面代码,将渲染出的完整 HTML 预先生成好,部署时直接将这些静态文件分发到 CDN 或静态服务器。用户访问时,服务器无需任何计算,直接返回现成的 HTML 文件。

代表框架:Gatsby、Next.js(getStaticProps / generateStaticParams)、VitePress、Docusaurus。

核心原理

构建阶段(npm run build)调用数据接口,执行页面组件代码,生成所有页面的静态 HTML 文件并输出到 dist 目录。部署后用户发起请求时,CDN 直接返回预渲染文件,全程无服务器动态计算。

markdown
开发完成,执行 npm run build / generate

构建阶段生成所有页面的 .html 文件

部署到 CDN 或静态服务器

用户访问 → CDN 直接返回现成的 HTML(无服务器计算)

优缺点

优点缺点
性能极高——HTML 来自 CDN 缓存或直接文件系统读取,无需服务器计算
安全性好——纯静态文件,无服务端代码执行面
SEO 友好——搜索引擎抓取到完整内容
数据更新需重新构建——内容变更必须重新触发构建
不适用于数据频繁变化的场景——每次更新都要全量重新生成

适用与不适用场景

本质权衡:SSG 以"放弃实时渲染能力"为代价,换取极高的加载性能和服务器端的安全性、稳定性。适合内容相对固定、以阅读为主的场景。

适用场景不适用场景
· 文档站(GitBook、VitePress、Docusaurus)
· 营销落地页、产品官网
· 博客、个人站点等内容相对稳定的网站
· 数据频繁更新的页面(如股票行情、实时数据看板)
· 需要根据用户身份个性化展示的内容
· 页面数量极多且无法穷举的场景(如电商搜索结果页)

5. ISR 渲染方案

ISR(Incremental Static Regeneration,增量静态再生)是 SSG 与 SSR 的混合方案,由 Next.js 在 9.5 版本引入。其核心思想是:不需要为所有页面在构建时一次性生成静态文件,而是在运行时按需"渐进式"生成静态页面——页面在首次访问时自动生成静态版本,后续请求直接复用缓存,只有超过指定周期或收到特定触发时才重新生成。

这种模式兼具 SSG 的高性能和 SSR 的灵活性,尤其适合页面数量庞大但又无法全部预构建的大型网站。

代表框架:Next.js(9.5+,通过 getStaticPropsrevalidate 参数,以及 App Router 中的 revalidate 导出或 revalidatePath / revalidateTag 按需失效机制)。

核心原理

ISR 的工作机制基于 HTTP stale-while-revalidate 缓存策略:

  • 首次访问(MISS):页面尚未生成静态版本,服务器实时渲染一次,生成静态 HTML 并存入缓存,同时标记下次过期时间。
  • 缓存期内(STALE):用户访问直接返回缓存的静态 HTML,速度与 SSG 完全一致。
  • 过期后(REVALIDATION):后台静默重新生成新版本,下次访问时用户获得最新页面,缓存无缝切换。

配置通过 Next.js 的 revalidate 字段控制,单位为秒(revalidate: 60 表示 60 秒后过期)。

markdown
用户首次访问页面(缓存 MISS)

服务器实时渲染,生成静态 HTML 并写入缓存

后续用户在 revalidate 周期内访问(缓存 HIT/STALE)

直接返回缓存的静态 HTML(极速加载)

超过 revalidate 周期后(后台触发 REVALIDATION)

后台静默重新渲染,更新缓存

下一位用户获得最新页面

ISR 的两种模式

模式说明适用场景
按路由周期失效整页按统一 revalidate 周期更新资讯列表、博客首页、商品分类页
按需失效(On-Demand ISR)通过 revalidatePath() / revalidateTag() 手动触发电商详情页(库存、价格变化时主动更新)

按需失效是 ISR 的重要能力——CMS 更新一篇文章时,不必触发全站重新构建,只需声明性清理特定路径或缓存标签的缓存即可。

优缺点

优点缺点
保留 SSG 的性能优势——缓存命中时页面加载极快,无服务器动态计算
支持内容更新——无需全站重新构建,页面可自动或手动刷新
适合大型站点——无需在构建时穷举所有页面,边际成本低
灵活的更新策略——按路由周期失效和按需失效可组合使用
数据更新有延迟——取决于 revalidate 周期设置,周期太长则内容不及时
架构复杂度较高——需要理解缓存策略与失效机制的配合
不适合极强实时性要求的场景——如股票行情、实时水位等毫秒级数据

适用与不适用场景

本质权衡:ISR 以"放弃绝对实时性"为代价,在 SSG 的高性能与 SSR 的灵活性之间取得平衡。适合那些内容需要更新、但更新频率不至于需要每次请求都实时渲染的场景。它将构建时生成的静态页面从"一次性产物"变成了"可持续更新的缓存资产"。

适用场景不适用场景
· 电商商品详情页、分类列表(数据更新频繁但非实时)
· 资讯文章列表和详情页(CMS 更新后按需刷新)
· 页面数量庞大、无法全部构建的大型内容站
· 需要良好 SEO 且首屏性能要求高的页面
· 对数据实时性要求极高的场景(股票、外汇、实时监控面板)
· 完全个性化、千人千面的页面(每次请求内容均不同)
· 数据完全公开但更新极频繁且不能有任何延迟的页面

6. PPR 渲染方案

PPR(Partial Prerendering,部分预渲染)是一种流式混合渲染策略,其核心思想是:将页面拆分为静态外壳动态组件,静态部分在服务端预渲染并立即返回,动态部分通过流式加载逐步呈现——用户无需等待所有内容就绪就能看到完整页面结构。

与 ISR 按页面粒度混合不同,PPR 的混合粒度是组件级别,同一个页面中不同组件可以采用不同的渲染策略。

核心原理

PPR 的实现依赖流式服务端渲染(Streaming SSR)能力。当服务端处理一个页面请求时,会分两部分工作:

  1. 静态外壳(Static Shell):不涉及动态数据的组件(如页面布局骨架、文章正文框架)立即渲染并生成完整 HTML,伴随动态组件的加载占位符一起返回,浏览器立即呈现页面框架。
  2. 动态组件:被加载占位符包裹的动态部分(如用户头像、评论列表)独立执行渲染,通过 HTTP 流式分块返回,浏览器逐步消费并替换对应的占位内容。

整个过程用户感知到的是页面在"一点点长出来",而非 SSR 中需要等待所有数据就绪才能看到任何内容。

markdown
用户访问 URL

服务端立即返回静态 HTML 外壳(页面骨架已呈现)

浏览器显示页面框架(包含动态组件加载占位符)

服务端流式推送动态组件渲染结果

动态部分逐一填充,页面完整呈现

与 ISR 的本质区别

维度ISRPPR
混合粒度页面级别——整页一起缓存/失效组件级别——不同组件独立缓存/流式渲染
缓存策略按页面整体缓存,存在统一 revalidate 周期静态外壳和动态组件各有独立的缓存语义
渲染时机首次访问时生成静态版本,后续复用静态外壳每次请求实时渲染,动态组件独立流式
内容新鲜度依赖 revalidate 周期,有一定延迟静态外壳每次请求新渲染,内容始终最新

PPR 可以看作是 ISR 思想的进一步细化——ISR 把整页当作一个整体进行增量更新,PPR 将渲染策略拆分到组件级别,让静态内容与动态内容各取所需。

:PPR 的概念最早由 Next.js 14 提出并实现,随后被其他框架逐步借鉴。当前生产级实现仍以 Next.js 为主,但其核心机制(静态外壳 + 流式动态组件)是一种与框架无关的渲染思想。

优缺点

优点缺点
首屏极快——静态外壳无任何动态延迟,页面框架立即呈现
动态内容无缝加载——占位符逐步填充,用户体验流畅
组件级混合灵活性——开发者按组件粒度控制渲染策略,无需整页一刀切
TTI 更优——HTTP 流式传输使页面可交互时间早于传统 SSR
需要框架深度支持——依赖流式渲染和组件级边界划分机制,配置门槛较高
调试复杂度高——静态外壳和动态组件的边界设计需要谨慎规划
属于前沿方案——各框架实现进度不一,API 稳定性仍在演进中
过度工程化风险——对简单页面反而引入不必要的架构复杂度

适用与不适用场景

本质权衡:PPR 以组件级拆分 + 流式渲染为手段,试图解决 SSR"等全部内容才呈现"和 SSG"动态内容必须等待构建"的固有矛盾。它代表了一种趋势——渲染策略从页面级别下沉到组件级别。但鉴于其前沿性,建议在非核心页面验证后再应用于生产环境。

适用场景不适用场景
· 动态内容与静态内容交织的页面(如文章页:正文静态 + 评论/点赞动态)
· 需要快速首屏的仪表盘类页面(静态框架 + 数据卡片流式加载)
· 大型内容站中同时包含个性化推荐和公共内容的混合页面
· 需要 SEO 同时又想优化可交互时间的复杂页面
· 页面结构简单、全部为静态内容的页面(SSG 足够,无需引入额外复杂度)
· 高度个性化、每个用户看到内容完全不同的页面(SSR 或 CSR 更合适)
· 追求框架 API 稳定性的生产环境项目(PPR 各框架实现进度不一)

7. 关键差异点分析与实践选择指南

7.1 五种方案综合对比

维度CSRSSRSSGISRPPR
HTML 生成时机客户端运行时每次请求时实时渲染构建时一次性生成首次访问时生成,后续按周期或按需更新服务端每次请求渲染静态外壳,动态组件独立流式返回
HTML 生成次数每次访问都重新生成每次请求都重新生成一次(构建时)首次生成后缓存,过期后重新生成静态外壳每次请求新渲染,动态组件独立缓存/流式
数据实时性实时(客户端获取)实时(请求时获取)静态(构建时固化)近实时(依赖 revalidate 周期)静态外壳实时,动态组件取决于其刷新策略
TTFB慢(需等 JS 下载执行后才开始接收内容)中等(服务端渲染需要时间)极快(CDN 直接返回)快(缓存命中时等同 SSG)快(静态外壳立即返回,无需等待动态数据)
SEO 友好度❌ 不友好(爬虫抓不到内容)✅ 友好(完整 HTML)✅ 友好(完整 HTML)✅ 友好(完整 HTML)✅ 友好(静态外壳包含完整页面结构)
服务器压力极低(仅提供静态资源)高(每次请求都需渲染)极低(纯 CDN)低(缓存命中时无服务端计算)中等(静态外壳每次需渲染,但可 CDN 缓存)
水合(Hydration)无需需要(水合开销大)少量或不需要需要(静态页面水合)部分需要(静态外壳无需或少量水合,动态组件独立渲染)
CDN 缓存能力可缓存 JS Bundle难以缓存(动态内容)完全支持(静态文件)支持(静态页面可缓存,动态部分走 API)支持(静态外壳可缓存,动态组件走流式)
构建时间影响无影响无影响随页面数量线性增长(万级页面构建慢)初始构建快,后续增量更新无影响(动态组件在请求时渲染)
适用场景强交互、登录后才能访问的内部系统需要 SEO 且数据相对动态的页面内容固定、访问量大的公开页面内容定期更新、页面数量大的内容站动静混合、组件渲染策略各异的复杂页面

渲染模式工作分布对比图:

记忆口诀:

  • CSR:构建少,服务少,客户端全包 → "客户端累死服务器清闲"。
  • SSR:构建少,服务较多 → "服务器打工,客户端躺平"。
  • SSG:构建较多,服务少 → "一次构建,多次享用"。
  • ISR:构建多,服务多 → "静态优先,动态按需"。
  • PPR:构建多,服务多 → "外壳先见,动态后长"。
markdown
┌─────────────────────────────────────────────────────────────────────────────┐
│                        渲染工作量分布:Build / Server / Client             │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  节点 ──►      Build (构建)        Server (服务端)      Client (客户端)    │
│            (编译/转译源代码)     (接收请求/处理数据)    (浏览器解析/交互)   │
│                                                                             │
│  ┌──────────────────────────────────────────────────────────────────────┐  │
│  │  CSR(客户端渲染)                                                    │  │
│  │    Build    ████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░    │  │
│  │    Server   ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░    │  │
│  │    Client   ██████████████████████████████████████████████████████    │  │
│  └──────────────────────────────────────────────────────────────────────┘  │
│                                                                             │
│  ┌──────────────────────────────────────────────────────────────────────┐  │
│  │  SSR(服务端渲染)                                                    │  │
│  │    Build    ████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░    │  │
│  │    Server   ██████████████████████████████████████████████████████    │  │
│  │    Client   ████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░    │  │
│  └──────────────────────────────────────────────────────────────────────┘  │
│                                                                             │
│  ┌──────────────────────────────────────────────────────────────────────┐  │
│  │  SSG(静态站点生成)                                                  │  │
│  │    Build    ██████████████████████████████████████████████████████    │  │
│  │    Server   ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░    │  │
│  │    Client   ████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░    │  │
│  └──────────────────────────────────────────────────────────────────────┘  │
│                                                                             │
│  ┌──────────────────────────────────────────────────────────────────────┐  │
│  │  ISR(增量静态再生)⭐ SSG + SSR 的混合                               │  │
│  │    Build    ████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░    │  │
│  │    Server   ████████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░    │  │
│  │    Client   ████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░    │  │
│  └──────────────────────────────────────────────────────────────────────┘  │
│                                                                             │
│  ┌──────────────────────────────────────────────────────────────────────┐  │
│  │  PPR(部分预渲染)⭐ 组件级混合渲染                                    │  │
│  │    Build    ████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░    │  │
│  │    Server   ████████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░    │  │
│  │    Client   ████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░    │  │
│  └──────────────────────────────────────────────────────────────────────┘  │
│                                                                             │
│  图例:■ = 该阶段工作量占比(■ 越多 = 工作量越大)                          │
│  Build = 源代码编译/转译    Server = 请求处理/渲染    Client = 浏览器执行   │
└─────────────────────────────────────────────────────────────────────────────┘

7.3 选型决策树

业务场景诊断

    ├── 是否需要公开搜索引擎收录?(SEO 是否重要)
    │       │
    │       ├── ✅ 是 → 是否要求毫秒级实时数据?
    │       │       │
    │       │       ├── ✅ 是 → ✅ SSR(必要时在 SSR 基础上引入 ISR)
    │       │       │
    │       │       └── ❌ 否 → 页面数量与更新频率如何?
    │       │               │
    │       │               ├── 页面少(百级以下)、内容固定不变
    │       │               │       → ✅ SSG(极致性能,CDN 零计算)
    │       │               │
    │       │               ├── 页面多(千级以上)或内容频繁更新
    │       │               │       → ✅ ISR(兼顾性能与更新能力)
    │       │               │
    │       │               └── 动静混合(部分静态 + 部分个性化)
    │       │                       → ✅ PPR(组件级混合渲染)
    │       │
    │       └── ❌ 否 → 是否需要登录后才可访问?(非公开页面)
    │               │
    │               ├── ✅ 是 → 强交互 / 富应用?
    │               │       ├── ✅ 是(后台系统、数据可视化等)
    │               │       │       → ✅ CSR(SEO 不重要,首屏后交互流畅)
    │               │       └── ❌ 否 → ✅ SSR + 客户端水合
    │               │
    │               └── ❌ 否(公开但无 SEO 需求,如内部文档工具)
    │                       → ✅ SSG 或 CSR(视交互复杂度而定)

7.4 常见落地陷阱与解决方案

① SSR 水合(Hydration)导致的页面闪烁与重复渲染

服务端渲染出的 HTML 与客户端首次渲染的 DOM 不一致时,React/Vue 会丢弃服务端 DOM 并重新渲染,导致页面闪烁(Flicker)和性能损耗。常见原因:服务端与客户端使用了不同的日期/随机值/语言环境;直接在组件顶层读取了 window / document 等浏览器对象。

解决方案:使用 useEffect(React)或 onMounted(Vue)包裹浏览器 API 调用,或启用懒水合(Lazy Hydration)策略。

② SSG 构建时间随页面数量爆炸性增长

站点达到数万级页面时,npm run build 可能耗时数小时,严重影响 CI/CD 发布节奏(电商类站点尤为突出)。

解决方案:采用 ISR 按需生成,或使用"预渲染白名单"策略(只预渲染高频访问页面,其余走 ISR/SSR)。

③ ISR 的 revalidate 周期设置两极化

周期过长 → 内容严重滞后(电商价格、库存错误);过短(如 revalidate: 1)→ 实际退化为 SSR,缓存形同虚设。

解决思路:为不同数据新鲜度要求的模块设置不同的刷新周期,通过页面级 ISR + 组件级 API 请求组合实现精细化控制,而非一刀切。

④ CSR/SSR 混用时的 SEO 策略不一致

同一 URL 在不同时间返回的 HTML 内容不一致(时而完整、时而空白),可能被搜索引擎判定为"内容作弊"。

解决思路:在项目初期确定统一的渲染策略,避免同一页面混合使用多种渲染模式。

⑤ SSR 的 API 请求"串行陷阱"

SSR 时组件的 API 请求按组件树串行发起(Component A → Component B → Component C),导致 TTFB 线性累加:100ms + 200ms + 150ms = 450ms。

解决思路:在服务端使用 Promise.all 并发发起所有数据请求,或利用 ISR 将渲染结果缓存。

⑥ PPR 流式渲染的组件边界划分失衡

边界划分过多导致架构碎片化,划分过少则退化为普通 SSR。

实践建议:优先将布局组件、导航、文章正文等固定内容划入静态外壳;将评论、点赞数、推荐内容等个性化组件划入流式动态组件。原则:"固定优先,动态例外"。

8. VitePress:一个典型的 SSG 实践案例

介绍完五种渲染方案,通过一个真实框架来验证理解,是巩固知识的最佳方式。VitePress 正是这样一个典型范例——它既不过于复杂,又足够完整地呈现了 SSG 的核心逻辑。

VitePress 是 Vue 官方推出的下一代文档框架,前身是 VuePress。它的设计目标非常明确:为文档站点提供极致的加载性能和极简的部署模型。Vue 官方文档本身正是跑在 VitePress 上,每天服务数百万次请求,从未需要一台应用服务器——这本身就是对 SSG 能力最有力的背书。

本章将剖析 VitePress 的渲染架构,从构建链路到运行时模型,从框架设计哲学到它给我们的选型启示。

8.1 VitePress 如何落地 SSG

VitePress 的 SSG 实践可以概括为三个关键词:构建时穷举、零运行时、纯静态分发

① 构建时穷举

VitePress 的文档内容以 Markdown 文件形式存在于 docs/ 目录下,每个 .md 文件对应一个路由页面。

执行 vitepress build 时,Vite 的插件链会遍历所有 Markdown 文件,逐一完成编译:Markdown 经过 markdown-it 解析为 HTML,嵌入的 Vue 组件被 Vue 3 runtime 渲染,最终产出独立的 .html 文件写入 dist/ 目录。

这个过程无需任何服务端参与,纯粹是本地构建工具的批量处理。

② 零运行时

生成的 dist/ 目录是一个完全自包含的静态站点——上传到任意静态文件服务器(或 CDN)即可提供服务。

服务器在运行时不需要执行任何应用代码,不需要连接数据库,不需要调用 API。用户访问任何一个页面,服务器只是从文件系统读取对应的 .html 文件并返回。这正是 SSG 的本质特征:构建时渲染完成,运行时零计算

③ 纯静态分发

得益于静态产物的极低访问成本,VitePress 支持直接部署到 CDN。用户的请求就近接入 CDN边缘节点,命中缓存时直接从边缘返回 HTML,源站服务器完全不参与响应。这种架构天然具备了极高的并发承载能力和极低的 TTFB,是任何需要服务端实时渲染的方案无法企及的性能基线。

8.2 核心原理

① 构建阶段(Build Time)

VitePress 使用 markdown-it 解析 Markdown,通过 vite-plugin-md 将 Markdown 文件作为 Vue 组件导入。每个 Markdown 文件本质上是一个 .vue 组件,构建时由 Vite 的插件链完成编译——Markdown 内容被编译为 HTML,Vue SFC 的 <script><style> 被编译为 JS/CSS,最后所有产物写入 dist 目录。

② 运行时(Runtime)

用户请求一个 VitePress 生成的页面时,服务器(任意静态服务器,或 VitePress 的 preview 命令)直接返回预编译好的 .html 文件。浏览器接收到的 HTML 已包含完整的页面内容——这是 SSG 的典型特征:零服务端计算,零动态渲染

同时,VitePress 在构建阶段会生成一个轻量的客户端 JS Chunk,包含 Vue runtime 和路由逻辑。页面加载后,Vue 完成"水合"(Hydration),页面进入可交互状态。这个 JS Chunk 极小(相比 Next.js 或 Nuxt 的 SSR bundle 轻量得多),因为它不需要包含数据获取逻辑或服务端渲染逻辑

③ 页面类型

VitePress 将页面分为两类,分别对应不同的文件路径:

页面类型文件位置路由渲染方式
文档页面docs/*.md/guide/getting-startedSSG(构建时生成)
布局页面docs/index.md/SSG(构建时生成)

所有页面都在构建时生成,运行时无任何服务端组件参与。

8.3 从 VitePress 学到的五件事

① 构建工具的选择直接决定开发体验上限

VitePress 的快,根源不在于"用了 Vite",而在于它正确地选择了只在构建阶段处理所有复杂计算。如果当初迁移时选择了保留 SSR 运行时,那么即使用 Vite 替换了 Webpack,数据获取和渲染的额外耗时依然存在。SSG 的性能优势,一半靠静态产物的缓存,一半靠构建工具链的高效执行。

② 框架的本质是 DSL 的编译器

VitePress 的 Markdown 拓展机制(frontmatter、代码高亮、内容嵌入)本质上是在 Markdown 语言之上封装了一层 DSL。框架作者不需要从零实现一个编程语言,只需要正确地"翻译"两种语法树——这是现代前端框架最强大的设计哲学之一:不要发明语言,要做好语言之间的桥梁

③ SSG 的极致简单是精心设计的结果

很多人以为 SSG"不需要架构",其实恰恰相反。VitePress 之所以能保持简洁,是因为它的设计者在架构层面做了大量克制:

  • 不做服务端路由、不做 API 层、不做运行时数据获取、不做用户认证……
  • 每少一个功能,就少一分运行时复杂度。但这些"少"是精心设计的结果,不是偷懒。

真正好的框架,是在满足需求的前提下,保持最小的运行时刻面。

④ 静态站点不等于"没有交互"

VitePress 构建出的页面水合后具备完整的 Vue 响应式能力,可以承载搜索框、主题切换、目录高亮等客户端交互。这些交互完全由客户端 JS Chunk 驱动,与构建阶段的渲染过程解耦。这意味着:SSG 不等于"只能做静态展示",它只是把"渲染"这件事放到了构建阶段,交互层仍然可以是完整的客户端应用

⑤ 文档站是最纯粹的 SSG 场景

VitePress 非常适合文档站,因为文档站天然具备 SSG 的所有优点:内容固定、页面数量可穷举、不需要实时数据、SEO 重要、访问量波动大(热门文档可能被大量并发访问,需要 CDN 缓存)。 反过来理解:如果文档站不用 SSG,而用 SSR,除了增加服务器成本,没有任何实际收益——这是判断"是否应该用 SSG"最简洁的思路:如果你的页面内容在构建时就已知且不变,SSG 就是最优解,不需要为它增加服务端运行时。

小结:

VitePress 是一个"恰到好处"的框架——它没有试图做 SSR,也没有试图做 ISR/PPR,它只是在 SSG 这条路上走到了极致。理解 VitePress 的渲染链路,能帮助我们更深刻地理解 SSG 的本质:构建时穷举所有可能,用极致的静态换极致的性能。 当你下次在选型时犹豫不决,不妨回想 VitePress 的设计哲学:最简单的架构,往往是正确答案。

9. 结语

在实际工程实践中,并不存在放之四海而皆准的"最优"渲染方案,只有与业务场景高度契合的适配策略。这一判断的底层逻辑,正是本文开篇所提出的核心公式:渲染方案 = 构建时机选择 × 渲染位置选择

CSR、SSR、SSG、ISR、PPR 五种方案,本质上都是在性能、实时性、SEO、架构复杂度这四个维度之间做权衡取舍。脱离具体业务场景谈技术选型毫无意义——后台管理系统强行套用 SSG 是自缚手脚,文档站点盲目使用 SSR 则是画蛇添足。

值得铭记的几条原则:

  • SSG 是性能的极限,代价是放弃实时性;SSR 是灵活性的极限,代价是服务器开销。ISR 和 PPR 则是在两者之间寻找平衡的艺术。
  • 渲染方案不是一成不变的。同一个产品的不同页面,完全可以采用不同的渲染策略——首页 SSG、商品详情页 ISR、文章正文 SSG + 评论 CSR,组合使用才是真实战。
  • 避免过度工程化。PPR 很酷,但简单页面用 SSR 就足够了。技术选型的成熟标志,是知道什么时候不该用它。

愿本文能帮助你在下一个项目中做出更清晰、更笃定的技术决策。