寫在前面
React、Vue 等現代化前端框架的大旗之下,CSR(Client-Side Rendering)模式深入人心:
CSR (Client-Side Rendering) - rendering an app in a browser, generally using the DOM.
前端部分幾乎全都是由客戶端動態渲染(客戶端執行 JS 代碼,動態創建 DOM 結構)出來的,例如:
<!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>
客戶端邏輯越重,初始化需要執行的 JS 越多,首屏性能就越慢,因而出現了更多的渲染模式探索:
-
SSR(Server-Side Rendering):服務端渲染,在服務端將 Web 應用渲染成 HTML
-
Rehydration:二次渲染,復用服務端渲染的 HTML DOM 結構和數據,在客戶端「溫啟動」JS 渲染
-
Prerendering:預渲染,在編譯時運行一個客戶端應用抓取其初始狀態生成靜態 HTML
一.CSR
CSR(Client-side rendering),即客戶端渲染,是指用 JS 直接在瀏覽器裡渲染頁面,包括數據請求、視圖模板、路由在內的所有邏輯都在客戶端處理:
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.其中出現的 FCP 與 TTI 是兩個重要的性能指標:
-
FCP(First Contentful Paint):用戶所請求的內容在屏幕上可見的時間點
-
TTI(Time To Interactive):頁面可交互的時間點
主要缺陷在於隨著應用程序的更新迭代,客戶端所要執行 JS 代碼量越來越多,前置的第三方類庫/框架、polyfill 等都會在一定程度上拖慢首屏性能,在(中低端)移動設備上尤為明顯
Code splitting、lazy-load 等優化措施能夠緩解一部分,但優化空間相對有限,無助於從根本上解決問題
此時,只有改變渲染模式才能創造更多的可能性
二.SSR
SSR(Server-Side Rendering)並不是什麼新奇的概念,前後端分層之前很長的一段時間裡都是以服務端渲染為主(JSP、PHP),在服務端生成完整的 HTML 頁面:
Server rendering generates the full HTML for a page on the server in response to navigation.
省去了客戶端二次請求數據的網絡開銷,以及渲染視圖模板的性能負擔。與 CSR 相比,其 FCP、TTI 通常會更快:

P.S.另一方面,服務端的網絡環境要優於客戶端,內部服務器之間通信路徑也更短
因為頁面邏輯(包括即時數據請求)和模板渲染工作都放在服務端完成,減少了客戶端的 JS 代碼量,流式文檔解析(streaming document parsing)等瀏覽器優化機制也能發揮其作用,在低端設備和弱網情況下表現更好。但在服務器上生成頁面同樣需要時間,會導致頁面內容響應時間(TTFB, Time to First Byte)變慢
一種辦法是可以通過 流式 SSR、組件級緩存、模板化、HTML 緩存 等技術來進一步優化
另一種辦法是繼續在渲染模式上探索,採用靜態渲染(Static Rendering)
三.Static Rendering
將生成 HTML 頁面的工作放到編譯時,而不必在請求帶來時動態完成。為每個 URL 預先單獨生成 HTML 文件,並進一步藉助 CDN 加速訪問:
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 傳遞靜態 HTML 文件
靜態渲染也並非完美,其關鍵問題在於*「靜態」*:
-
需要為每個 URL 單獨生成一份 HTML 文件:對於無法預知所有可能的 URL,或者存在大量不同頁面的網站,靜態渲染就不那麼容易,甚至根本不可行
-
只適用於偏靜態內容:對於動態的、個性化的內容作用不大
另外,還有個與靜態渲染相似的概念,叫預渲染(Prerendering)
Prerendering
主要區別在於,靜態渲染得到的頁面已经是可交互的,無需在客戶端額外執行大量 JS 代碼,而預渲染必須經客戶端渲染才真正可交互:
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.
也就是說,禁用 JS 後,靜態渲染的頁面幾乎不受影響,而預渲染的頁面將只剩下超鏈接之類的基本功能
四.Rehydration
Rehydration 模式將 CSR 與 SSR 結合起來了,服務端渲染出基本內容後,在客戶端進行二次渲染(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.
例如:

實際渲染流程如下:

注意 bundle.js 仍然是全量的 CSR 代碼,這些代碼執行完畢頁面才真正可交互。因此,這種模式下��FP(First Paint)雖然有所提升,但 TTI(Time To Interactive)可能會變慢,因為在客戶端二次渲染完成之前,頁面無法響應用戶輸入(被 JS 代碼執行阻塞了)
對於二次渲染造成交互無法響應的問題,可能的優化方向是 增量渲染(例如 React Fiber),以及 漸進式渲染/部分渲染
Trisomorphic Rendering
如果把 [Service Worker](/articles/理解 web-workers/) 也考慮進來的話,還有一種涉及三方的渲染模式:
SSR + CSR + ServiceWorker rendering = Trisomorphic Rendering
如下圖:

首先通過流式 SSR 渲染初始頁面,接著由 Service Worker 根據路由規則,藉助 SSR 渲染出目標 HTML 頁面:
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.
主要優勢在於能夠跨三方共享模板渲染和路由控制邏輯:
This approach works best when you can share the same templating and routing code between the server, client page, and service worker.
五.總結
每種渲染模式都有一定優勢,也有其局限性和缺點,實際場景中需要在多種因素之下權衡選擇:

暫無評論,快來發表你的看法吧