啥是 XXR ?認識前端項目渲染模式們
這是我們團隊 @許濱楠 同學做的內部分享,科普當下流行的 CSR、SSR、SSG 等渲染模式的原理與優劣勢,相當有料。
PS:我們是字節游戲中臺前端團隊,日常學習氛圍濃厚,最近聽說要 10-7-5 了,還有大量 HC,歡迎自薦。
一、啥是「啥是 XXR ?」?
前端研發中有許多常見場景,根據不同的構建、渲染過程有不同的優劣勢和適用情況。如現代 UI 庫加持下常用的 CSR、具有更好 SEO 效果的 SSR (SPR)、轉換思路主打構建時生成的 SSG、大架構視野之上的 ISR、DPR,還有更少聽到的 NSR、ESR 等等。
諸多方案的提出和完善帶來了更多的技術選型可能,迅速涌現的生態支持也讓不同方案的開發體驗、心智負擔漸漸趨于便捷、開發者無感。
但:
- 各種方案的運作模式是怎樣的?
- 不同場景下應該如何選擇方案?
- 各種方案能帶來的優勢是什么?造成的劣勢或者當下的不足有什么?
- 有怎樣的開發工具、類庫、框架可以支持?
希望這篇文章能幫助解決上述這樣的疑惑,這就是「啥是 XXR ?」。
若尚不完整或有失偏頗,歡迎討論 & 指教。
二、渲染模式——概念與對比
這里所說的 ✌🏻 渲染模式 ✌🏻,包括:
頁面 / 應用在開發完成之后的產物編譯方式;
部署上線之后的服務形態;
資源存儲與分發的方式;
用戶訪問時的啟動與渲染過程;
這幾方面不同的實現和規范。
本節將介紹各種渲染模式的基本特點、運作方式,還有對應的優缺點比較。
2.1 CSR for Client Side Rendering
顧名思義的“客戶端渲染”,是當下用于渲染各類 UI 庫構建的前端項目的最常見方案。
2.1.1 啥是 CSR?
在這種模式下,頁面托管服務器只需要對頁面的訪問請求響應一個類似這樣的空頁面:
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8" />
- <!-- metas -->
- <title></title>
- <link rel="shortcut icon" href="xxx.png" />
- <link rel="stylesheet" href="xxx.css" />
- </head>
- <body>
- <div id="root"><!-- page content --></div>
- <script src="xxx/filterXss.min.js"></script>
- <script src="xxx/x.chunk.js"></script>
- <script src="xxx/main.chunk.js"></script>
- </body>
- </html>
可以看到頁面中留出一個用于填充渲染內容的視圖節點 (div#root),并插入指向項目編譯壓縮后的 JS Bundle 文件的 script 節點和指向 CSS 文件的 link.stylesheet 節點等。
瀏覽器接收到這樣的文檔響應之后,會根據文檔內的鏈接加載腳本與樣式資源,并完成以下幾方面主要工作:執行腳本、進行網絡訪問以獲取在線數據、使用 DOM API 更新頁面結構、綁定交互事件、注入樣式,以此完成整個渲染過程。
2.1.2 優劣相依
CSR 模式有以下幾方面優點:
- 「UI 庫支持」:常用 UI 方案如 React、Vue,默認的應用形態都是 SPA (for Single Page Application),是交互程度高、動態化強的 Web 應用,CSR 很好地滿足了這種應用形態的需要,并在主流技術棧中擁有廣泛支持;
- 「前后端分離」:視圖交互和具體數據解耦,有賴于這種應用形態的出現和普及,做到前后端職能清晰明確,更容易維護與協作;
- 「服務器負擔輕」:從示例可見,CSR 場景下的頁面托管服務只需要對訪問請求返回一個每次部署后固定的空白頁,其他的資源加載和渲染交給瀏覽器完成,項目靜態資源(bundle、css、assets)則都是部署在 CDN 上的,服務器負擔輕、響應快,且更利于資源的終端和 CDN 緩存;
優劣相依,這樣的模式也具有以下缺陷:
- 「呈現速度受限」:基于上面特點,盡管更輕的服務負荷帶來了更快的訪問響應速度,但 CSR 頁面的呈現速度和效果容易受到限制——用戶瀏覽器拿到模板 HTML 之后對文檔和 JS 代碼的解析耗時、邏輯執行耗時、接口請求耗時、加載靜態資源工作對 CDN 情況、網絡環境、終端瀏覽器性能的依賴,都能很大程度上影響甚至阻塞頁面渲染,破壞用戶體驗;
- 「不利于 SEO (for Search Engine Optimization)」:爬蟲請求 CSR 的頁面時會受限從服務器得到不含內容的空頁面,不利于站點在搜索引擎上的信息采集和曝光(但現在頭牌搜索引擎如谷歌、百度、必應等,其爬蟲能力已經可以部分支持 CSR SPA 的頁面內容爬取)。在非常動態的、交互性很強而輕實際內容的情景下,SEO 友好程度或許并不重要——即使重要,也有部分解決方法,如結合 meta / template 插入一些重要信息,還有后面將會提到的 SSG;
- 「*較低的安全性」:了解到的一個論調是 CSR 場景下,頁面更容易受到 XSS (for Cross-Site Scripting) 攻擊,通過發掘頁面內可以干預邏輯代碼的入口,劫持用戶的會話并進行惡意操作。CSR 在此方面相對安全性較低的一個考慮點,或許是有更多的邏輯代碼需要在瀏覽器上直接運行并可見,讓不懷好意者更有可乘之機——但在如今越來越多的安全工具、瀏覽器安全部署、代碼混淆方案背景下,魔與道孰高孰低其實一直在較量之中。
2.2 SSR for Server Side Rendering
“事物的發展是螺旋上升的。”
2.2.1 啥是 SSR?
SSR 的概念,即與 CSR 相對地,在服務端完成大部分渲染工作,其實這就是一開始還沒有如今的前端的時候,頁面的呈現方式——服務器在響應站點訪問請求的時候,就已經渲染好可供呈現的頁面。但不同于刀耕火種時代通過后端模板之類方案生成頁面,如今的 SSR 能力已經越來越強大,部分情況下甚至能做到“開發者低感知”的狀態——開發 SSR 與 CSR 項目并沒有太多不同(如廠內框架 Jupiter 的 SSR 支持)。這有賴于社區生態的發展,上面提到 CSR 的框架/類庫(當然還有沒提到,筆者本身也很少實踐的 Angular、Svelte 等),都有非常優秀的 SSR 方案。
2.2.2 簡述原理
— “在服務端完成頁面渲染,豈不是要在服務端模擬一個瀏覽器?”
— “是,但不完全是。”
像 React、Vue 這樣的 UI 生態巨頭,其實都有一個關鍵的 Virtual DOM (or VDOM) 概念——瀏覽器 DOM API 太慢,先自己建模處理視圖表現與更新、再批量調 DOM API 完成視圖渲染更新。這就帶來了一種 SSR 方案:
VDOM 是自建模型,是一種抽象的嵌套數據結構,也就可以在 Node 環境(或者說一切服務端環境)下跑起來,把原來的視圖代碼拿來在服務端跑,通過 VDOM 維護,再在最后拼接好字符串作為頁面響應,生成文檔作為響應頁面,此時的頁面內容已經基本生成完畢,把邏輯代碼、樣式代碼附上,則可以實現完整的、可呈現頁面的響應。
在此基礎上,另外對于一些需要在客戶端激活的內容,如 Vue 實例接管組件行為、React Effect 在客戶端的觸發執行,則由編譯時生成 Bundle,并在響應頁面內的超鏈接腳本額外附著。
2.2.3 先揚后抑
SSR 方案發展在 CSR 之后再次得到推進,很大程度上就是為了解決 CSR 的一些問題,這也是 SSR 相較之下突出的優勢:
- 「呈現速度和用戶體驗佳」:SSR 對比 CSR,少了很多頁面到達瀏覽器之后的解析、資源加載、邏輯代碼執行的過程,用戶拿到響應內容后,這份內容基本已經是可以呈現的頁面,首屏時間大大縮短;
- 「SEO 友好」:SSR 服務對于站點訪問請求響應的是填充過的頁面,其中已經有許多站點信息和數據可供爬蟲直接識別,搜索引擎優化自不必說;
- 老規矩,先揚后抑。優勢之上,SSR 也帶來了一些局限:
- 「引入成本高」:SSR 方案重新將視圖渲染的工作交給了服務器做,這就引入了新的概念和技術棧(如 Node),并且帶來了更高的服務器硬件成本和運維成本;
- 「響應時間長」:對比 CSR 只需要響應早已準備好的空頁面,SSR 在完成訪問響應的時候需要做更多的計算和生成工作,因此其請求響應時間更長,同時還受限于前置數據接口的響應速度,一項關鍵指標 TTFB (Time To First Byte) 將變得更大;
- 「首屏交互不佳」:又是那句話,“SSR 的用戶啟動體驗好,但不完全好”。雖然 SSR 可以讓頁面請求響應后更快在瀏覽器上渲染出來,但在首幀出現,需要客戶端加載激活的邏輯代碼(如事件綁定)還沒有初始化完畢的時候,其實是不可交互的狀態,同樣影響用戶體驗;
- 「傳統開發思路受限」:斟酌之下還是將其列出作為 SSR 的局限性,既然主要頁面內容是在服務端完成渲染的,那么對于瀏覽器(或者 Hybrid、Webview 之下的宿主)環境的獲知和相關操作就會受到局限,一些操作不得不延遲到客戶端激活之后才得以進行,這也是導致上一個局限點的原因。
2.2.4 SPR for Serverless Pre-Rendering
無服務預渲染,這是 Serverless 話題之下的一項渲染技術。SPR 是指在 SSR 架構下通過預渲染與緩存能力,將部分頁面轉化為靜態頁面,以避免其在服務器接收到請求的時候頻繁被渲染的能力,同時一些框架還支持設置靜態資源過期時間,以確保這部分“靜態頁面”也能有一定的即時性。
這是對 SSR 服務運行計算成本高、服務負載大的一種針對性優化,如今也已經有不少前沿框架支持,開發者可以非常方便地引入。
2.3 SSG for Static Site Generation
某種形式上的縫合怪 —— but in a good way.
2.3.1 啥是 SSG?
說它是縫合怪,是因為它與 CSR 一樣,只需要頁面托管,不需要真正編寫并部署服務端,頁面資源在編譯完成部署之前就已經確定;但它又與 SSR 一樣,屬于一種 Prerender 預渲染操作,即在用戶瀏覽器得到頁面響應之前,頁面內容和結構就已經渲染好了。當然形式和特征來看,它更接近 SSR。
如果說 CSR 與 Prerender 差異在于渲染工作重心的抉擇,同是 Prerender 的 SSR 和 SSG 則是渲染——或者是這其中非常重要的“注水”——填充內容操作在時機上的抉擇。
又或者從另一個角度來說,不同于把大部分渲染工作留到請求時做的 CSR 和 SSR,SSG 在站點項目構建部署的時候,就把頁面內容大致填充好了。
最終 SSG 模式的有點真正“返璞歸真”的意思,原本日益動態化、交互性增強的頁面,變成了大部分已經填充好,托管在頁面服務 / CDN 上的靜態頁面。
2.3.2 平衡得夠好嗎?
SSG 兼收了傳統 CSR 和 SSR 的優點的同時,對這兩者的短板也做到較好的互補。服務負擔低、加載性能與體驗佳、SEO 友好,這些 SSG 的取各家之長的優勢此處不必單獨分析,但還有一些好處源自這個模式本身:
頁面內容都是靜態生成過的,頁面部署只需要簡單的頁面托管服務器,甚至只需要放在 CDN 之上,大量減少了動態性,還有服務器對頁面加載、渲染工作的干預,也就讓惡意攻擊少了很多可乘之機;
SSG 的不足之處也值得提出來討論:
隨著應用的拓展和復雜化,預渲染頁面的數量增長速度很快。SSG 項目有較高的構建和部署開銷,應用越復雜,需要構建出來的靜態頁面就會越多,對于功能豐富的大型站點,每次構建需要渲染成千上萬個頁面都是有可能的,這必然帶來較高的部署、更新成本;
高度靜態化帶來非即時性,用戶訪問到的頁面內 SSG 生成的部分,確保有效性的時間節點是上一次構建,使該模式下的應用失去了部分時效性,這部分缺陷需要通過定時構建、或者部分非 SSG 來彌補,這也是 SSG 的主要問題。
2.4 BTW
既然說到了,那就說一說。
還有一些 XXR,并不是 CSR / SSR 那樣的大陣營或整體方案,而是一些性能策略、優化手段,同時還依賴更大架構下的技術能力支持,這里羅列并簡單介紹。
2.4.1 NSR for Native Side Rendering *
Native 就是客戶端,萬物皆可分布式,可以理解為這就是一種分布式的 SSR,不過這里的渲染工作交給了客戶端去做而不是遠端服務器。在用戶即將訪問頁面的上級頁面預取頁面數據,由客戶端緩存 HTML 結構,以達到用戶真正訪問時快速響應的效果。
NSR 見于各種移動端 + Webview 的 Hybrid 場景,是需要頁面與客戶端研發協作的一種優化手段。
2.4.2 ESR for Edge Side Rendering *
Edge 就是邊緣,類比前面的各種 XSR,ESR 就是將渲染工作交給邊緣服務器節點,常見的就是 CDN 的邊緣節點。這個方案主打的是邊緣節點相比核心服務器與用戶的距離優勢,利用了 CDN 分級緩存的概念,渲染和內容填充也可以是分級進行并緩存下來的。
ESR 之下靜態內容與動態內容是分流的,邊緣 CDN 節點可以將靜態頁面內容先響應給用戶,然后再自己發起動態內容請求,得到核心服務器響應之后再返回給用戶,是在大型網絡架構下非常極致的一種優化,但這也就依賴更龐大的技術基建體系了。
2.4.3 ISR for Incremental Site Rendering
直譯,增量式網站渲染。也很好理解,就是對待頁面內容小刀切,有更細的差異化渲染粒度,能漸進、分層地進行渲染。常見的選擇是:對于重要頁面如首屏、訪問量較大的直接落地頁,進行預渲染并添加緩存,保證最佳的訪問性能;對于次要頁面,則確保有兜底內容可以即時 fallback,再將其實時數據的渲染留到 CSR 層次完成,同時觸發異步緩存更新。
對于“異步緩存更新”,則需要提到一個常見的內容緩存策略:Stale While Revalidate,CDN 對于數據請求始終首先響應緩存內容,如果這份內容已經過期,則在響應之后再觸發異步更新——這也是對于次要元素或頁面的緩存處理方式。
基于此,CDN 做的事情是直接響應用戶的每個請求,并在用戶觸發 fallback、當前預渲染過的頁面過期失效且再次被用戶訪問的時候更新緩存的預渲染資源;客戶端在感知上則有以下不好的體驗:
- 訪問到沒被預渲染過的次要內容觸發 fallback,需要進行 CSR,加載較慢;
- 訪問到之前被預渲染過,但已經過期且未更新的頁面,會先得到過期的緩存響應,在觸發 CDN 異步緩存更新之后再次訪問才能得到新資源,造成體驗上的前后不一致。
2.4.4 DPR for Distributed Persistent Rendering
DPR 是一家云計算公司 Netlify 在幾個月前 (2021/04) 才發出的一個 「新提案」(https://github.com/jamstack/jamstack.org/discussions/549),它是基于 ISR 基本模型的一種升級,也是針對 ISR 在即時性上的不足的優化。
看過定義和提案之后我對 DPR 的譯名斟酌不定,大概是“分布式持續/持久化渲染”,因為其利用了 CDN 分布節點進行渲染請求——分布(而且渲染時機也是分布在構建 / 請求時的);又是一個按需漸進的過程——持續;同時在 CDN 基礎上架設了緩存能力——持久化。
這聽起來在分布式方面跟上面剛剛說到的 NSR 有點像,又跟 ESR 很接近,實際上這里的分布式跟前者完全不同,但與 ESR 確實有很多相似之處,甚至可以說是其升級版本。這里借用提案中的圖簡單介紹:
左邊虛線框是構建過程,中間 DPR functions 可以是一些 Serverless 的或是在核心頁面服務器上的按需構建函數,然后是 CDN 緩存節點,最后到用戶瀏覽器。
Build 階段就會完成 generate site 的操作,這一步并不會完成所有構建,而只生成關鍵部分的資源,部署到頁面托管服務或者 CDN 之上;而對于其他內容,有一個按需處理的過程—— CDN 會在收到首個訪問請求的時候實時要求構建,并將最新構建結果返回給用戶,同時將這部分內容加入原有緩存資源中;緩存的資源也會在下一次構建更新的時候被失效。
三、如何選擇
— 現在我選眼花了 😵💫
這些方案并非完全并列,較難完全“分支化決策”,這里列出幾個考慮中的關注點:
特性關注點:
是否關注 SEO
- 是:需要 Pre-Render,純 CSR 不可取
- 否:無限制
是否具有豐富可交互性、需要用戶能力、差異化渲染
- 是:CSR 較方便、SSR 加載快
- 否:Pre-Render 系列保證加載體驗
頁面結構 & 路由是否復雜、數據更新是否頻繁、是否依賴實時數據接口
- 是:首先排除 SSG、如果內容不能拆解,ISR、DPR 也不便接入
- 否:無限制
依賴關注點:
是否接受引入服務器運維成本
- 是:無限制,SSR 可沖
- 否:失去 SSR 選項
舊有實現中是否有瀏覽器依賴如 UI 框架內對 DOM、BOM,或 Hybrid 場景下的 JSBridge 的使用
- 是:SSR、SSG 受限
- 否:無限制
以上考慮點都不產生限制,那就選用優缺點最能滿足項目特征的、有比較完備的技術基建支持的模式吧~
本文轉載自微信公眾號「Tecvan」