圖解 SSR 等 6 種前端渲染模式

關(guān)注「前端向后」微信公眾號(hào),你將收獲一系列「用心原創(chuàng)」的高質(zhì)量技術(shù)文章,主題包括但不限于前端、Node.js以及服務(wù)端技術(shù)
寫(xiě)在前面
React、Vue 等現(xiàn)代化前端框架的大旗之下,CSR(Client-Side Rendering)模式深入人心:
CSR (Client-Side Rendering) – rendering an app in a browser, generally using the DOM.
前端部分幾乎全都是由客戶端動(dòng)態(tài)渲染(客戶端執(zhí)行 JS 代碼,動(dòng)態(tài)創(chuàng)建 DOM 結(jié)構(gòu))出來(lái)的,例如:
<!DOCTYPE html>
<html>
<head>
<title>My Awesome Web App</title>
<meta charset="utf-8">
</head>
<body>
<div id="app"></div>
<script src="bundle.js"></script>
</body>
</html>
客戶端邏輯越重,初始化需要執(zhí)行的 JS 越多,首屏性能就越慢,因而出現(xiàn)了更多的渲染模式探索:
SSR(Server-Side Rendering):服務(wù)端渲染,在服務(wù)端將 Web 應(yīng)用渲染成 HTML
Rehydration:二次渲染,復(fù)用服務(wù)端渲染的 HTML DOM 結(jié)構(gòu)和數(shù)據(jù),在客戶端“溫啟動(dòng)”JS 渲染
Prerendering:預(yù)渲染,在編譯時(shí)運(yùn)行一個(gè)客戶端應(yīng)用抓取其初始狀態(tài)生成靜態(tài) HTML
一.CSR
CSR(Client-side rendering),即客戶端渲染,是指用 JS 直接在瀏覽器里渲染頁(yè)面,包括數(shù)據(jù)請(qǐng)求、視圖模板、路由在內(nèi)的所有邏輯都在客戶端處理:
Client-side rendering (CSR) means rendering pages directly in the browser using JavaScript. All logic, data fetching, templating and routing are handled on the client rather than the server.
渲染流程如下圖:

P.S.其中出現(xiàn)的 FCP 與 TTI 是兩個(gè)重要的性能指標(biāo):
FCP(First Contentful Paint):用戶所請(qǐng)求的內(nèi)容在屏幕上可見(jiàn)的時(shí)間點(diǎn)
TTI(Time To Interactive):頁(yè)面可交互的時(shí)間點(diǎn)
主要缺陷在于隨著應(yīng)用程序的更新迭代,客戶端所要執(zhí)行 JS 代碼量越來(lái)越多,前置的第三方類庫(kù)/框架、polyfill 等都會(huì)在一定程度上拖慢首屏性能,在(中低端)移動(dòng)設(shè)備上尤為明顯
Code splitting、lazy-load等優(yōu)化措施能夠緩解一部分,但優(yōu)化空間相對(duì)有限,無(wú)助于從根本上解決問(wèn)題
此時(shí),只有改變渲染模式才能創(chuàng)造更多的可能性
二.SSR
SSR(Server-Side Rendering)并不是什么新奇的概念,前后端分層之前很長(zhǎng)的一段時(shí)間里都是以服務(wù)端渲染為主(JSP、PHP),在服務(wù)端生成完整的 HTML 頁(yè)面:
Server rendering generates the full HTML for a page on the server in response to navigation.
省去了客戶端二次請(qǐng)求數(shù)據(jù)的網(wǎng)絡(luò)開(kāi)銷,以及渲染視圖模板的性能負(fù)擔(dān)。與 CSR 相比,其 FCP、TTI 通常會(huì)更快:

P.S.另一方面,服務(wù)端的網(wǎng)絡(luò)環(huán)境要優(yōu)于客戶端,內(nèi)部服務(wù)器之間通信路徑也更短
因?yàn)轫?yè)面邏輯(包括即時(shí)數(shù)據(jù)請(qǐng)求)和模板渲染工作都放在服務(wù)端完成,減少了客戶端的 JS 代碼量,流式文檔解析(streaming document parsing)等瀏覽器優(yōu)化機(jī)制也能發(fā)揮其作用,在低端設(shè)備和弱網(wǎng)情況下表現(xiàn)更好。但在服務(wù)器上生成頁(yè)面同樣需要時(shí)間,會(huì)導(dǎo)致頁(yè)面內(nèi)容響應(yīng)時(shí)間(TTFB, Time to First Byte)變慢
一種辦法是可以通過(guò)流式 SSR、組件級(jí)緩存、模板化、HTML 緩存等技術(shù)來(lái)進(jìn)一步優(yōu)化
另一種辦法是繼續(xù)在渲染模式上探索,采用靜態(tài)渲染(Static Rendering)
三.Static Rendering
將生成 HTML 頁(yè)面的工作放到編譯時(shí),而不必在請(qǐng)求帶來(lái)時(shí)動(dòng)態(tài)完成。為每個(gè) URL 預(yù)先單獨(dú)生成 HTML 文件,并進(jìn)一步借助CDN加速訪問(wèn):
Static rendering happens at build-time and offers a fast First Paint, First Contentful Paint and Time To Interactive – assuming the amount of client-side JS is limited. Unlike Server Rendering, it also manages to achieve a consistently fast Time To First Byte, since the HTML for a page doesn’t have to be generated on the fly.
渲染流程如下圖:

P.S.SSR 第一部分的 Server Rendering 渲染工作變成了 Streaming 傳遞靜態(tài) HTML 文件
靜態(tài)渲染也并非完美,其關(guān)鍵問(wèn)題在于“靜態(tài)”:
需要為每個(gè) URL 單獨(dú)生成一份 HTML 文件:對(duì)于無(wú)法預(yù)知所有可能的 URL,或者存在大量不同頁(yè)面的網(wǎng)站,靜態(tài)渲染就不那么容易,甚至根本不可行
只適用于偏靜態(tài)內(nèi)容:對(duì)于動(dòng)態(tài)的、個(gè)性化的內(nèi)容作用不大
另外,還有個(gè)與靜態(tài)渲染相似的概念,叫預(yù)渲染(Prerendering)
Prerendering
主要區(qū)別在于,靜態(tài)渲染得到的頁(yè)面已經(jīng)是可交互的,無(wú)需在客戶端額外執(zhí)行大量 JS 代碼,而預(yù)渲染必須經(jīng)客戶端渲染才真正可交互:
Static rendered pages are interactive without the need to execute much client-side JS, whereas prerendering improves the First Paint or First Contentful Paint of a Single Page Application that must be booted on the client in order for pages to be truly interactive.
也就是說(shuō),禁用 JS 后,靜態(tài)渲染的頁(yè)面幾乎不受影響,而預(yù)渲染的頁(yè)面將只剩下超鏈接之類的基本功能
四.Rehydration
Rehydration 模式將 CSR 與 SSR 結(jié)合起來(lái)了,服務(wù)端渲染出基本內(nèi)容后,在客戶端進(jìn)行二次渲染(Rehydration):
Often referred to as Universal Rendering or simply “SSR”, this approach attempts to smooth over the trade-offs between Client-Side Rendering and Server Rendering by doing both. Navigation requests like full page loads or reloads are handled by a server that renders the application to HTML, then the JavaScript and data used for rendering is embedded into the resulting document.
例如:

實(shí)際渲染流程如下:

注意bundle.js仍然是全量的 CSR 代碼,這些代碼執(zhí)行完畢頁(yè)面才真正可交互。因此,這種模式下,F(xiàn)P(First Paint)雖然有所提升,但 TTI(Time To Interactive)可能會(huì)變慢,因?yàn)樵诳蛻舳硕武秩就瓿芍?,?yè)面無(wú)法響應(yīng)用戶輸入(被 JS 代碼執(zhí)行阻塞了)
對(duì)于二次渲染造成交互無(wú)法響應(yīng)的問(wèn)題,可能的優(yōu)化方向是增量渲染(例如React Fiber),以及漸進(jìn)式渲染/部分渲染
Trisomorphic Rendering
如果把Service Worker也考慮進(jìn)來(lái)的話,還有一種涉及三方的渲染模式:
SSR + CSR + ServiceWorker rendering = Trisomorphic Rendering
如下圖:

首先通過(guò)流式 SSR 渲染初始頁(yè)面,接著由 Service Worker 根據(jù)路由規(guī)則,借助 SSR 渲染出目標(biāo) HTML 頁(yè)面:
It’s a technique where you can use streaming server rendering for initial/non-JS navigations, and then have your service worker take on rendering of HTML for navigations after it has been installed. This can keep cached components and templates up to date and enables SPA-style navigations for rendering new views in the same session.
主要優(yōu)勢(shì)在于能夠跨三方共享模板渲染和路由控制邏輯:
This approach works best when you can share the same templating and routing code between the server, client page, and service worker.
五.總結(jié)
每種渲染模式都有一定優(yōu)勢(shì),也有其局限性和缺點(diǎn),實(shí)際場(chǎng)景中需要在多種因素之下權(quán)衡選擇:

參考資料
Rendering on the Web
