Contents
1 第一章 从内容到页面:还有另一半故事
在上一篇文章中(Web的形态演化(一):从页面到内容——WordPress改变了什么?),我们以网页中的”内容”为切入点,介绍了内容从早期静态网页时代与页面紧密耦合,到逐渐成为独立存在对象的发展过程。
在静态网页时代,内容和页面几乎是一体的,一个 HTML 文件既保存内容,也决定最终呈现效果。而随着 WordPress 等 CMS 的出现,内容开始从页面中分离出来,成为可以独立存储和管理的对象。后来,前后端分离更是进一步强化了这一趋势,内容甚至可以完全脱离具体页面而存在。
至此,Web 完成了一次重要的变化:网页中的内容不再依附于页面本身。 不过,这只是整个故事的一半。
其实,对于用户来说,无论内容保存在 HTML 文件、数据库,还是通过 API 获取,这些对于他们而言都是无感的,他们最终看到的始终都是一张完整的网页。而用户浏览器既不会直接显示数据库中的一条记录,也不会直接理解一段 JSON 数据,它最终需要的,依然是能够完成页面展示的一份HTML结果。
换句话说,内容可以独立存在,但网页最终仍然需要在某个时刻,以某种方式被“生成”出来。
于是,一个更自然的问题随之浮现:这些独立存在的内容,究竟是在什么时候、又是通过什么方式,变成我们最终看到的网页?
这个问题在静态网页时代似乎并不存在,因为页面本身就是最终结果;但当内容逐渐与页面解耦之后,“生成页面”反而成为了一项独立的工作。
更有意思的是,虽然二十多年来用户浏览网页的方式并没有发生本质变化,但网页的生成过程却一直在演进:有的时候,它在用户访问之前就已经准备好了;有的时候,需要等到用户发起访问时才临时生成;还有的时候,它会在运行时由浏览器完成生成。
从用户的角度来看,我们始终浏览着同样的网页;但在 Web 的内部,网页究竟是在什么时候、由谁生成出来的,却经历着不断的演化。
2 静态网页时代:当“生成”还不是一个问题
如果把视角拉回到 Web 的早期阶段,会发现一个有趣的事实:“网页是如何生成的”这个问题,在那个时代其实并不存在。 原因很简单——在最初的 Web 中,网页本身就是一个已经完成的结果。
在静态网页时代,一个网站本质上就是一组分散在服务器上的 HTML 文件。当用户访问某个 URL 时,服务器所做的事情非常直接:找到对应的文件,然后原样返回给浏览器。没有数据库参与,没有运行时逻辑,也没有所谓“组装页面”的过程。
换句话说,那时的网页并不是“被生成出来的”,而是本来就已经存在;浏览器所接收到的,也不是一个“生成结果”,而是一份已经写好的 HTML 文档,它所负责的事情也很单纯:解析这份文档,并将其渲染成用户看到的页面。
在这个阶段,页面、HTML 文件以及最终展示结果几乎可以看作同一个对象。 一个 URL 对应一个 HTML 文件,一个 HTML 文件对应一个页面,这种关系简单而直接,以至于整个系统根本不需要引入”生成页面”这一层抽象。
如果从今天的角度回头来看,这种结构甚至会显得有些”过于简单”:没有中间层,没有动态计算,也没有运行时生成,一切都在用户访问之前就已经确定好了。但也正因为如此,它拥有一种后来很多系统都很难做到的特性——确定性:无论什么时候访问,同一个 URL 返回的永远都是同一份 HTML 文件;浏览器所看到的内容,也始终保持一致。因此,在那个时代,Web 的复杂性几乎全部集中在”内容如何编写”上,而不是”页面如何生成”上。
不过,这种简单并不是没有代价。
随着互联网的发展,网站承载的信息越来越多。新闻网站每天都会发布新的文章,企业网站不断增加产品介绍,个人博客持续积累内容,论坛和社区甚至每时每刻都在产生新的帖子。网站开始从几十个页面发展到几百个、几千个,甚至更多。这时,人们逐渐发现,真正不断增长的,其实并不是页面结构,而是页面中的内容。
以博客为例,每篇文章都拥有几乎相同的页面布局:标题、正文、发布时间、作者信息、评论区域……真正不同的,往往只是文章内容本身。
然而,在静态网页时代,每增加一篇文章,就意味着新增一个完整的 HTML 文件;如果需要修改整个网站的导航栏、页脚或侧边栏,又必须逐个修改所有相关页面。
问题并不在于这种方式无法实现,而在于所有变化都只能直接作用在最终的 HTML 文件上:内容更新,需要修改 HTML;页面布局调整,需要修改 HTML;甚至多个页面之间共享的结构,也只能依靠人工维护。
直到这时,人们才逐渐意识到一个此前从未认真思考过的问题:既然许多网页拥有相同的页面结构,而真正变化的只是其中的内容,那么为什么还要把它们写成一个又一个独立的 HTML 文件? 换句话说,我们真正需要维护的,也许并不是页面本身,而是页面中的内容,以及决定这些内容如何组织和展示的规则。
当这种思考出现时,Web 也迎来了第一次重要的转折——页面开始不再是被直接保存的最终对象,而是变成了一个可以根据内容”产生”出来的结果。
也正是在这一刻,“生成网页”第一次成为了一个真正需要解决的问题。
3 从“保存页面”到“生成页面”
第二章最后,我们提到了一个重要的变化:当网站规模越来越大时,人们意识到需要长期维护的并不是一份又一份结构相似的 HTML 文件,而是其中真正不断变化的内容。于是,网页第一次不再被视为需要直接保存的最终对象,而是开始成为一种可以根据内容动态产生的结果。
那么,一个新的问题自然出现了:如果页面不再提前保存,它究竟是在什么时候产生的?
最早回答这个问题的,并不是某一种具体技术,而是一种全新的 Web 思想,它认为,真正需要长期保存的应该是内容,而页面则可以等到用户访问时,再根据内容临时生成。也就是说,服务器不再像静态网页时代那样,仅仅负责把一个已经存在的 HTML 文件返回给浏览器,而是开始承担另一项新的工作:根据已有内容,实时生成最终的 HTML 页面——以 WordPress 为代表的 CMS,正是这种思想最典型的实践。
对于浏览器来说,这一切几乎没有任何变化:用户访问一个网址,浏览器最终收到的,依然是一份完整的 HTML 文档,页面最终呈现出来的效果,与静态网页时代并没有本质区别。真正发生变化的,其实是在浏览器看不到的地方:过去,服务器返回的是一个已经存在的 HTML 文件;而现在,服务器返回的,却是一份刚刚生成出来的 HTML 页面。
这意味着,当用户每发起一次访问,服务器都会经历一次新的处理过程:它首先找到用户请求对应的内容,然后根据预先设计好的页面模板,将标题、正文、作者、发布时间等信息填充进去,最后再生成完整的 HTML,并发送给浏览器。整个过程都发生在用户等待网页加载的短短几百毫秒之内。
对浏览器来说,这份 HTML 从哪里来并不重要,它始终只是收到一份标准的 HTML 文档,然后负责将它渲染出来。
因此,从用户视角来看,Web 几乎没有发生变化。真正改变的,是网页诞生的时间:在静态网页时代,网页早在用户访问之前就已经存在;而到了 WordPress 时代,网页直到用户发起访问之后,才开始真正”诞生”。
也正因为如此,Web 第一次拥有了真正意义上的”动态”能力:新增一篇文章,不再意味着新增一个 HTML 文件,而只是新增一条内容记录;修改整个网站的页面布局,也不再需要逐个修改所有页面,只需要调整统一的页面模板即可。
网页开始真正变成了一种运行时生成的结果,而不是一种需要长期维护的文件。
今天我们通常把这种在用户发起请求后,由服务器动态生成 HTML 页面再返回给浏览器的方式称为 SSR(Server-Side Rendering,服务端渲染)。例如大家熟悉的 WordPress,每次访问文章时,都会读取数据库、执行 PHP 程序,最后生成完整的 HTML 页面返回给浏览器,这就是典型的 SSR 模式:

需要注意的是,SSR 并不是 WordPress 发明的新技术,而是后来人们对这一类页面生成方式所形成的统一称呼。对于本文而言,更重要的是理解它背后的演化思想:网页开始从“提前存在”变成了“请求时生成”。
不过,这种方式在解决一个问题的同时,也带来了新的代价。既然每一次访问都需要重新生成页面,那么服务器就必须一次又一次地重复相同的工作。对于访问量较小的网站来说,这种额外开销几乎可以忽略;但随着网站规模不断扩大,这种”每访问一次,就生成一次”的模式,也开始逐渐暴露出新的问题。
于是,Web 的演化并没有停止。人们开始思考:对于那些几乎不会变化的网页,真的有必要每次访问都重新生成一次吗?
4 当“每次生成一次”开始变得不必要
在 WordPress 为代表的 CMS 体系中,Web 第一次进入了“动态生成页面”的阶段。页面不再提前以 HTML 文件的形式存在,而是在用户发起访问时,由服务器根据内容与模板实时生成。这种方式在很长一段时间里都是合理的,对于内容持续变化的网站来说,这种模型解决了一个核心问题:内容和页面结构的解耦之后,页面可以被动态重建。
但随着 Web 的进一步发展,一个新的问题开始逐渐浮现:并不是所有内容都在频繁变化。恰恰相反,大量网页在生成之后,其内容在很长时间内都保持稳定。例如一篇博客文章、一份文档说明页、一个产品介绍页,它们一旦发布之后,往往只会偶尔修改,而不会持续变化。
但在 SSR 的模型下,这些页面仍然被当作“每次访问都需要重新生成”的对象来处理。也就是说,即使内容完全没有变化,服务器仍然必须重复执行同样的过程:读取内容 → 套用模板 → 生成 HTML → 返回给浏览器。从结果来看,每次生成出来的 HTML 几乎完全一致。
问题也正是在这里逐渐显现出来的:如果每一次生成的结果都是一样的,那么“每次都重新生成一次”这件事本身,是否仍然是必要的?
这个问题一旦被提出,整个模型的前提就开始动摇。因为 SSR 的本质是:每次请求都重新计算一次页面。而现实情况却变成了:大部分请求,其实只是在重复计算一个已经确定的结果。
于是,一个新的优化思路开始出现:既然页面在大多数情况下是稳定的,那么是否可以在第一次生成之后,把这个结果保存下来?如果页面没有发生变化,那么后续的请求,就不再需要重新执行生成过程,而是可以直接返回已经生成好的结果。也就是说,Web 开始从“每次生成”走向“生成一次”。
这就是静态生成开始出现的背景,在这种模型中,页面的生成时机被进一步提前,不再发生在用户访问时,而是发生在更早的阶段——当内容被更新或网站被构建时。生成过程不再依赖每一次请求,而是变成了一次性的计算过程。之后的访问,只是在重复使用已经生成好的结果。
从用户的角度来看,这一切依然没有变化。他们看到的仍然是一张完整的网页,加载速度甚至可能更快。但在系统内部,网页的生成方式已经发生了微妙但关键的变化——它不再是“请求驱动生成”,而是变成了“提前生成 + 重复使用”。
今天通常将这种“在用户访问之前提前生成好 HTML 页面,并在后续请求中直接复用”的方式称为 SSG(Static Site Generation,静态站点生成)。例如 Hexo、Hugo 等静态博客系统,就是典型的 SSG 模式。它们会在发布网站时提前生成所有 HTML 页面,之后用户访问时,Web 服务器只需要直接返回已经生成好的文件,而无需再临时生成页面:

它与 SSR(Server-Side Rendering)的关键区别并不在于“是否生成页面”,而在于页面生成发生的时间点:SSR 发生在请求时,而 SSG 则提前到发布阶段完成。
从演化角度来看,SSG 并不是对 SSR 的替代,而是针对“相同内容会被重复生成”这一问题所做的一种结构性优化。
但与此同时,这种方式也引出了一个新的问题:如果页面在构建阶段就已经生成完成,那么当内容发生变化时,又该如何更新这些已经生成好的页面?
Web 的演化并没有在这里停止,相反,这个问题将进一步推动下一次结构变化的发生。
5 当“生成”开始进入浏览器
在 SSG 的模型中,网页的生成被进一步提前:页面不再在用户访问时动态生成,而是在构建阶段就已经完成。之后的请求,只是简单地返回已经生成好的 HTML 结果。从结果上看,这种方式非常高效——页面不再重复计算,服务器压力大幅下降,响应速度也更稳定。
但随着这种模式的发展,一个新的问题开始逐渐显现:如果页面已经在构建阶段提前完成,那么那些只有在用户真正访问之后才会发生的变化,又该如何处理?换句话说,SSG 解决了”生成太慢”的问题,但也让另一类需求开始变得更加突出:并不是所有页面内容,都能够在构建阶段提前确定。
对于内容稳定的网站来说,这并不是问题。但 Web 的另一个重要特征是:交互。用户不只是阅读页面,他们还会操作页面。点击按钮、切换状态、筛选内容、加载更多数据……这些行为都意味着:页面不仅仅是“展示结果”,还需要在运行过程中不断发生变化。
而在 SSG 的模型中,这种变化很难被自然表达。因为页面已经在生成阶段被固定下来,它不再具备“重新生成”的能力。于是,一个新的分界开始变得清晰:静态生成解决的是“页面如何被构建”,但没有回答“页面如何在使用过程中变化”。
这个问题,最终被推到了浏览器这一侧。随着 JavaScript 能力的不断增强,浏览器不再只是一个“展示 HTML 的工具”,而开始具备执行复杂逻辑的能力。页面不再完全依赖服务器来生成,而是可以在加载完成之后,由浏览器继续对其进行“补充”和“改造”。
在这种模式下,浏览器开始承担一部分原本属于服务器的工作。数据可以在页面加载后再获取;界面可以在用户操作时再重新计算;甚至整个页面结构,都可以在运行过程中重新组织。也就是说,网页的“生成”不再只发生一次,也不再只发生在服务器上,而是开始进入一个新的阶段:运行时生成(Runtime Generation)。
而这一变化,最终导向了一个非常明确的模型:页面不再是服务器输出的最终结果,而是浏览器中不断运行的应用程序。在这个模型里,服务器的角色被进一步弱化。它不再负责生成完整页面,而只是提供数据与接口。真正的页面结构与变化逻辑,被完全交给了浏览器中的 JavaScript。
于是,Web 的生成逻辑第一次彻底发生了迁移:从服务器,移动到了客户端。
今天通常将这种“页面主要由浏览器中的 JavaScript 在运行时生成”的方式称为 CSR(Client-Side Rendering,客户端渲染)。近年来大量基于 React、Vue 等现代前端技术开发的 Web 应用,都属于这种模式。用户访问网站时,浏览器首先下载应用代码,然后再由 JavaScript 在客户端生成页面内容:

与 SSR 和 SSG 不同,CSR 的核心变化并不在于“生成发生的时间”,而在于生成发生的位置从服务器迁移到了浏览器。
在这种模式下,服务器更多负责提供数据接口,而页面结构与渲染逻辑则由浏览器在运行时完成。
但这种模式也带来了新的结构性变化,因为当生成完全发生在浏览器中时,最初加载的页面往往只是一个“空壳”。真正的内容,需要在页面加载之后,通过额外的逻辑逐步构建出来。这使得页面的首次呈现和最终状态之间,开始出现明显的差异。Web 的结构,也因此变得更加动态,但同时也更加复杂。
于是,“页面应该由谁来生成”这个问题,也开始变得不再只有一种答案。
6 从“选择生成方式”到“分配生成方式”
在前面的演化过程中,Web 的变化始终围绕着同一个核心问题展开:网页应该在什么时候被生成?
静态网页时代,这个问题甚至不存在,因为网页本身就是已经存在的文件。到了 WordPress 为代表的 CMS 阶段,网页开始在用户访问时被动态生成,生成发生在请求时。随后,SSG 将生成提前到构建阶段,让页面在访问之前就已经被固定下来。而 CSR 则进一步改变了生成发生的位置,将这一过程转移到了浏览器的运行时。
如果从这一条主线来看,Web 的演化似乎一直在做同一件事:不断调整“生成发生的时间点”和“发生的位置”。但当 Web 发展到复杂应用阶段之后,一个更根本的问题开始出现。现实中的网页,往往并不是一个统一的整体。
以一个典型的现代页面为例,它通常同时包含多种不同性质的内容:有些部分几乎不会变化,例如文章正文或说明文档;有些部分会随时间更新,例如推荐内容或列表数据;有些部分依赖用户状态,例如登录信息或个性化内容;还有一些部分需要频繁交互,例如评论区、弹窗或局部组件。
如果仍然坚持“一个页面只使用一种生成方式”,就会出现一个结构性问题:任何单一的生成模型,都只能优化某一类场景,而无法同时适配所有部分。
例如,对于新闻首页、论坛等需要实时展示最新内容的网站,如果完全采用静态生成,那么每一次内容更新都需要重新构建页面,显然缺乏灵活性;但如果全部采用请求时生成,即使像网站导航、页脚、文章正文这类长期不变的内容,也需要在每次访问时重新计算,又会带来额外的运行开销。
而有些页面更强调实时交互,例如后台管理、在线文档或即时聊天,更适合由浏览器在运行时处理;同时,另一些页面则更强调内容展示,如果也完全依赖浏览器生成,则在首屏加载速度、搜索引擎收录以及社交平台预览等方面往往存在明显劣势。
这说明,不同类型的内容、不同的使用场景,对于网页生成方式的需求并不相同。真正的问题已经不再是“哪一种生成方式最好”,而是“页面中的哪一部分,更适合采用哪一种生成方式”。
于是,Web 的演化也进入了新的阶段:页面开始不再作为一个整体被生成,而是根据不同部分的特点,将生成工作分配到不同的时间、不同的位置去完成。
在这种新的结构下,一个页面可以同时存在多种生成逻辑:稳定的部分可以在构建阶段提前生成;动态的部分可以在请求时由服务器生成;交互相关的部分则可以在浏览器运行时完成。这些不同的生成方式不再彼此竞争,而是开始在同一个页面中共存。
从外部来看,用户依然看到的是一张完整的网页。但在系统内部,这张网页已经不再是一个统一的生成结果,而是由多个不同生成机制协同拼装而成的结构。
当 Web 发展到这一阶段之后,“生成网页”这个问题本身也发生了变化,它已经不再是“SSR、SSG 或 CSR 三选一”,而变成了一个结构性问题:页面中的不同部分,应该分别在哪里生成?
现代 Web 中常见的 React / Next.js / Nuxt 等框架,本质上并不是一种新的生成范式,它们只是将前面所讨论的三种“生成发生位置”——请求时生成(SSR)、提前生成(SSG)以及运行时生成(CSR)——在同一个系统中进行组合与分配的结果。
7 生成权的迁移:Web 的统一解释模型
如果回到最初的问题,其实整篇文章始终围绕着同一个核心:网页到底是在什么时候、在哪里被生成的?
在静态网页时代,这个问题并不显性,因为网页本身就是已经存在的结果。用户访问 URL,本质上只是获取一个已经写好的 HTML 文件,“生成”并不是一个独立过程。 随着 Web 结构逐渐复杂,页面与内容开始解耦,这个问题才逐渐浮现。但即便如此,它在很长一段时间内仍然只是系统内部的实现细节,而没有成为一个独立的设计维度。
直到 Web 进入更复杂的阶段,“网页如何被生成”才真正变成一个可以被显式设计的问题。 从这一刻开始,Web 的演化不再围绕具体技术展开,而是围绕同一个核心维度展开:页面生成发生的位置与时机。如果将这一维度抽象出来,大致可以得到下面这样一条演化轴:

在这一维度之下,不同阶段的 Web,只是在选择不同的取值方式,而非引入新的问题本身:最终,无论是 SSR、SSG、CSR,还是它们的组合形式,本质上都只是这一维度在不同约束条件下的不同表达。
从这个角度来看,所谓 Web 的技术演化,并不是一个线性发展的技术史,而是同一个问题在系统中的不同位置被不断重新分配的过程。
而当我们回到这一抽象层,会发现所有复杂概念最终都可以被还原为一个更简单的问题:在这一阶段,页面生成这件事,由系统中的哪一部分承担?
这,就是整个 Web 形态演化的统一解释。
注:SSR、SSG 与 CSR 并不是严格意义上的时间顺序演化链条,而是对“网页生成发生在什么时刻、由系统中的哪一部分负责”这一问题的三种抽象划分方式。在实际 Web 发展过程中,这些方式长期并存,并在不同场景下不断被组合与重用。本文为了叙事的连贯性,按照这一问题的演化路径进行了线性展开,但这只是表达上的顺序,并不意味着这些技术在历史上就是按这一顺序出现的。