啥是 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 模式有以下幾方面優點:

優劣相依,這樣的模式也具有以下缺陷:

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 也帶來了一些侷限:

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。

最終 SSG 模式的有點真正 “返璞歸真” 的意思,原本日益動態化、交互性增強的頁面,變成了大部分已經填充好,託管在頁面服務 / CDN 上的靜態頁面。

2.3.2 平衡得夠好嗎?

SSG 兼收了傳統 CSR 和 SSR 的優點的同時,對這兩者的短板也做到較好的互補。服務負擔低、加載性能與體驗佳、SEO 友好,這些 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、當前預渲染過的頁面過期失效且再次被用戶訪問的時候更新緩存的預渲染資源;客戶端在感知上則有以下不好的體驗:

  1. 訪問到沒被預渲染過的次要內容觸發 fallback,需要進行 CSR,加載較慢;

  2. 訪問到之前被預渲染過,但已經過期且未更新的頁面,會先得到過期的緩存響應,在觸發 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 會在收到首個訪問請求的時候實時要求構建,並將最新構建結果返回給用戶,同時將這部分內容加入原有緩存資源中;緩存的資源也會在下一次構建更新的時候被失效。

三、如何選擇

—— 現在我選眼花了 😵‍💫

這些方案並非完全並列,較難完全 “分支化決策”,這裏列出幾個考慮中的關注點:

特性關注點:

依賴關注點:

以上考慮點都不產生限制,那就選用優缺點最能滿足項目特徵的、有比較完備的技術基建支持的模式吧~

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/gkSrys6Uw73Xno28eIgY5Q