渲染页面的 N 种姿势 | 从模板引擎到新式服务端渲染
小麦2025年05月08日2214 字
本期聊聊 Web 页面渲染技术的演进过程。
大家好我是小麦,今天来聊一个咱们既熟悉又陌生的话题,如何渲染一个页面?
但我暂时不想谈论浏览器的底层工作原理。
而是想回顾一下从早期到现代,前端页面从装配到传输至用户浏览器的过程中,发生了哪些变化,相信大家看完会有不少收获。
早在 1994 年,宇宙最强语言 PHP 横空出世,在那个前后端还没有分离的时代,Web 开发者是这样写网页程序的。
PHP 作为一款服务器脚本语言,就像是一个强大的文本处理器,你可以在 HTML 里使用 php 标记打洞,执行服务端代码,最终生成完整的 HTML 代码。
当然这种前后端代码杂糅的方式写起来会很痛苦,但为后来模板渲染的技术路线提供了思路。
时间来到 2000 年,MVC 架构开始流行。
MVC 是将 Web 应用程序分离成模型(Model)、视图(View)和控制器(Controller)三个部分,其中模型和控制器用于编排业务逻辑,而视图则专注于用户界面。
基于 MVC 架构的典型代表,有 Java 阵营的 Struts,Ruby 阵营的 Ruby on Rails,Python 阵营的 Django,PHP 阵营的 CodeIgniter 以及更后来的 Node.js 阵营的 Express 等等。
此时,Web 开发者已经可以在 MVC 框架的视图层,使用一个叫做 “模板引擎” 的东西,快速渲染前端界面。
以咱们前端更熟悉的 Express 框架为例,我们使用 EJS 作为模板引擎,通过 res.render 方法执行服务端渲染,对应的视图模板如右图所示。
如果只看模板代码,你会发现它看起来很像 PHP,但是模板的职责是更单一的,就是通过模板语法把数据 “渲染” 到页面上。
当然,模板语法还是太简单了,无法胜任复杂的交互需求。那有没有办法把它加强呢?
如果我们把 MVC 中的 V 单独拿出来做,前后端便发生了分离。
得益于 JavaScript 和 Web 技术的快速发展,前端终于可以直接在浏览器里渲染更复杂的页面,至此,客户端渲染时代到来了。
客户端渲染的常见做法是,
服务端只提供带有 js 脚本引用的基本 HTML 骨架。
浏览器加载 HTML 主文档以及 js 脚本执行页面渲染。
而前后端通信则使用异步 HTTP 接口完成。
后续所有的视图切换和交互处理,均由前端负责。
而这,就是我们非常熟悉的单页应用架构(SPA)。
前端工程师已经可以使用诸如 Backbone.js、Angular、React 和 Vue 构建页面了。
这时,“渲染” 过程已经完全从服务端的模板渲染,迁移到了客户端渲染(CSR)。
客户端渲染有很多好处,无论是前后端职责的完全分离,还是处理复杂交互的能力,都是以前无法比拟的。
直到现在,客户端渲染也非常流行。
但是,客户端渲染也存在很多弊端,并非渲染技术的最终形态。
比如对 SEO 不友好,首屏加载的白屏时间太长,低端设备容易出现掉帧卡顿现象,用户体验欠佳等等。
于是我们不得不考虑,是否可以只让服务端做首屏渲染,而让客户端接管后续的渲染呢?
工程师们确实这么做了,而新一代的 “服务端渲染技术” 就此诞生。
不同于早期模板渲染的是,服务端渲染的核心做法是:当用户访问时,服务端连同业务数据和页面结构,一并渲染到 HTML 主文档中。
随即下发到客户端,浏览器将立即呈现页面效果。
但此时用户尚不能进行交互,直到前端通过 js 脚本重新为页面元素绑定交互事件。在一些框架中,也把这个过程叫做 “水合”。
服务端生成的 HTML 要能给客户端使用,如此默契的配合要归功于同构(isomorphic)设计。
即前端框架既要能运行在客户端,还要能运行在服务端,而 Node.js 让这一想法成为了可能。
以 React + Express 为例,左边是在服务端使用 React 的 renderToString,渲染 App 组件到主文档的 root 元素的代码。
右边是在客户端使用 React 的 hydrate 挂载同一个 App 组件到 root 元素的代码。
可以看到 App 组件可以同时运行在服务端和客户端,而这就是服务端渲染的核心两个步骤。
服务端渲染技术看似打破了前后端完全分离的协作模式,但前端的本质工作,依然聚焦于如何构建交互界面这个命题。
为了进一步提升服务端渲染的效率和终端用户体验,如今,服务端渲染技术(广义上)又演化出了许多变体。不妨简单了解一下,也许会用得到。
静态站点生成(SSG),是一种服务端在构建阶段预先生成多个静态页面的方案。适用于内容固定的资讯、博客或 CMS 系统。它减少了服务端运行时的资源消耗,用户体验也非常优秀。
支持 SSG 的典型框架有 Hugo、Hexo 和 Gatsby 等。
增量静态再生(ISR)是一种对 SSG 的改进,相比于 SSG 在构建阶段渲染页面,增量静态再生可以在内容发生变化时重新渲染,而不需要跑一次构建发布流程。支持 ISR 的典型框架有 Next.js、Nuxt.js 等
流式渲染是一种对传统 SSR 的改进,一般 SSR 会默认渲染整个 HTML 文档,而使用 Suspense 组件可以转变成流式渲染。
比如左边的代码将文章和评论内容同时渲染,而右边的代码用 Suspense 组件隔离评论区。
这样服务端可以先将渲染好的文章组件发送给客户端,而评论区则进行异步渲染、流式传输。
还有一个和服务端渲染技术非常相关的名词,叫 React Server Component,即 React 服务器组件。和它对应的概念是客户端组件(CSC),客户端组件我们很熟悉,即纯前端组件。
RSC 是 React 框架特有的一种组件,可以在服务端和客户端之间传输特定格式的数据,而非 HTML 代码。
它看起来像这样。相比直接传输 HTML,RSC 可以实现更高效的服务器端渲染和网络传输。
还有一种更先进的服务端渲染方案,叫部分预渲染(PPR),当前只在 Next.js 的 canary 版本中提供。
这个图能直观地解释 PPR 是什么。我们之前已经提到 ISR,图中紫色部分即为 ISR 渲染好的静态内容,而蓝色部分则为用户请求到来时才渲染的动态内容。
PPR 可以说是把动静结合发挥到了极致,整合了 ISR、RSC 和流式渲染的特性,最大程度上保证了用户体验。
从早期的模板渲染,到客户端渲染,再回到服务端渲染,前后端分分合合,仿佛又回到了原点,但此原点并非彼原点。
而是 Web 开发者们不断打破局限,新技术超越旧技术的必然。
而这些渲染技术的底层逻辑,即前端开发的意义,已经不仅仅是把页面呈现出来,更重要的是对于用户体验的极致追求。
好了,那么这就是本期视频的全部内容了,如果觉得好的话记得给个一键三连。
我是小麦,一位热爱分享的前端工程师,期待用通俗易懂的内容带你探索丰富多彩的世界。
评论