【翻譯】Web渲染概述

本文簡單介紹了web應用各種渲染方案,其中包括客戶端渲染、服務器端渲染等各種渲染方案。文章翻譯自:https://developers.google.com...。由我所在的團隊共同翻譯完成,併發布在前端技術公衆號:方凳雅集上,轉載於此。方凳雅集是阿里CBU前端技術專業號,有興趣的小夥伴可以關注一發。

1. 起航

作爲開發人員,我們經常面臨影響應用程序整個架構的決策。我們必須做出的核心決策之一是在什麼地方實現業務邏輯和渲染邏輯。這可能很困難,因爲有很多不同的方法來構建網站。對這個領域的理解來自於過去幾年我們在一些大型網站的工作。從廣義上說,我們鼓勵開發人員選用帶有rehydration(下一小節有解釋)的服務器端渲染或靜態化渲染。爲了更好理解我們在做決定時所選擇的架構,需要對每種方法和術語有充分的理解,通過不同渲染方式的頁面性能可以幫助我們理解它們之間的差異。

2. 術語

渲染
SSR:服務器端渲染。
CSR:客戶端渲染。
Rehydration:在服務器端渲染的dom樹和數據的基礎上,瀏覽器端利用JavaScript再次渲染。
Prerendering:在構建時生成靜態HTML和頁面的初始狀態。

性能
TTFB:Time to First Byte —— 瀏覽器發出資源請求到接受到資源第一個字節的時間。
FP:First Paint —— 頁面打開到可視內容第一個像素渲染出來的時間。
FCP:First Contentful Paint —— 頁面打開到頁面主要內容可見的時間。
TTI:Time To Interactive —— 頁面打開到變得可以交互的時間。

這裏只是簡單的介紹了這些性能術語,想要詳細的瞭解它們,可以看一下我們之前寫的兩篇文章性能優化篇——以用戶爲中心的指標(一)和性能優化篇——以用戶爲中心的指標(二)。

3. 服務器端渲染

服務器端渲染:打開頁面時服務器將完整的HTML生成好了並返回。這避免了在瀏覽器端取數然後渲染所產生的消耗,因爲這些事情已經在服務器端響應用戶之前就已經做好了。

服務器端渲染會帶來快速的FP和FCP,在服務器端處理業務邏輯和渲染邏輯,可以避免向瀏覽器發送大量JavaScript,這有助於實現快速的TTI,這種方法適用於各種設備和網絡環境,如果你開啓了一些流瀏覽器優化,比如文檔採用流的方式解析(streaming document parsing)。

clipboard.png

對於服務器端渲染,用戶不太可能會去等瀏覽器執行其他的耗CPU的JavaScript代碼執行完畢再去操作,因爲頁面內容已經顯示出來了。在第三方js無法避免時(廣告),雖然服務器端渲染可以減少FP和FCP渲染的JavaScript消耗,但是可能會給接下來要執行的js帶來一定的“負擔”。服務器端渲染的主要缺點在於,渲染需要消耗時間,所以可能TTFB會比較大。

服務器渲染是否可以滿足應用程序,很大程度上取決於您正在構建的體驗類型。關於服務器渲染與客戶端渲染的哪個更好的爭論從未停過。但是我們需要記住的時,我們可以有選擇讓一些頁面進行服務器端渲染,其他頁面使用客戶端渲染。一些網站使用這種混合渲染模式就取得了不錯的效果,比如Netflix對他的登錄頁面採用了服務器端渲染,同時爲重交互的頁面預取js,爲那些重交互並且客戶端渲染的頁面加快頁面加載的機會。

許多現代框架、庫和架構使得同一個應用同時在服務器端和瀏覽器端都能渲染成爲可能。這些技術雖然可以用於服務器端渲染,但需要注意的是,在服務器和客戶端上進行渲染的體系結構具有非常不同的性能特徵和權衡。React用戶可以用它的renderToString方法或者其他基於該方法的框架進行服務器端渲染,比如Next.js; Vue用戶可以去看它的服務器端渲染指南或者Nuxt; Angular用戶可以去看Universal。

4. 靜態化渲染

靜態化渲染:在構建(build)時將頁面中不會變化的內容直接渲染成出來,然後打到HTML中去。在瀏覽器端需要執行的js有限的假設下,該方法能夠提供快速的FP、FCP和TTI。與服務器端渲染不同,它還能提供快速的TTFB,因爲服務器端不需要生成HTML。一般而言,靜態化渲染需要爲每個URL生成一個單獨的HTML,當用戶訪問的時候,直接將預先渲染好的HTML返回就好。另外渲染出的HTML也可以部署到CDN上,通過edge caching(邊緣緩存)緩存做一些優化。對於不瞭解edge caching的同學,可以去看一下我們之前寫的React緩存小記,裏面有關於它的介紹。

clipboard.png
靜態化渲染也有不同的方案,比如像Gatsby這樣的工具旨在讓開發人員感覺他們的應用程序是動態渲染的,而不是在構建過程中產生靜態的HTML;像Jekyl和Metalsmith這樣的工具擁抱的靜態特性,提供了很多模板驅動的方法。

靜態化渲染需要爲每個URL生成單獨的HTML,這是它的一個缺點。如果您無法提前預測這些URL的內容,或者或一個網站存在大量的URL,靜態化渲染可能是不合適的。對於靜態化渲染,React用戶可能對Gatsby、Next.js的static export或者Navi比較熟悉。

這裏我們需要重點理解一下靜態化渲染和預渲染之間的差異:靜態化渲染的頁面在瀏覽器端變得能夠交互之前只需要執行很少的js代碼,甚至不需要;而預渲染雖然能夠實現快速的FP、FCP,但應用必須在瀏覽器端執行js代碼才能變得可交互。

如果您不能確定到底是使用靜態化渲染還是預渲染,那就測試一下唄,在應用加載的時候,禁止JavaScript的加載和執行。禁止JavaScript後,對於靜態化渲染,頁面的大多數功能還是能夠正常運行;而對於預渲染,頁面可能只有一些基礎的功能能夠工作,比如連接跳轉。

另一個有用的測試方式是通過Chrome的開發者工具DevTools減慢網絡速度,觀察在頁面變爲交互之前下載了多少JavaScript。預渲染通常需要更多的JavaScript,並且複雜度往往也比使用漸進增強的靜態化渲染要高。

5. 服務器端渲染VS靜態化渲染

服務器端渲染不是一顆銀彈,它的動態特性會帶來巨大的計算開銷。服務器端渲染會加大TTFB或發送雙倍的數據(比如將客戶端的State數據打到HTML中去),在React中,renderToString會很慢,因爲它是同步並且單線程的。爲了讓服務器端渲染能夠”正確“運行,我們需要關注組件緩存、內存消耗、memoization技術應用和其他問題。通常情況你可能需要多次渲染同一個應用——一次在服務器端,一次在客戶端。服務器端渲染只能讓需要展示的內容顯示的的更快,並沒有減少工作量。

服務器端渲染可以根據不同的URL生成不同的內容,相較於靜態化渲染,它的速度可能會慢一些。其實我們可以做一些工作來緩解這個問題,服務器渲染 + HTML緩存可以大大減少渲染時間。服務器端渲染的優勢在於它能夠獲取實時數據,並且所能處理的請求集比靜態化渲染要大。因爲靜態化渲染只能處理那些提前能夠預測內容的頁面,對於需要個性化的頁面(千人千面),靜態化渲染就無法處理了。

在構建PWA時,服務器端渲染也有用武之地,服務器端對頁面片段進行渲染,前端使用Service Worker進行緩存。

6. 客戶端渲染

客戶端渲染:是指在瀏覽器中直接使用JavaScript來渲染頁面,所有的邏輯、數據的獲取、模板和路由都是在客戶端而不是服務器上處理的。

在移動端,客戶端渲染很難獲得並保持一個較快的渲染速度。有時我們只需要做很少的工作,就能讓客戶端渲染的性能與服務器端渲染的性能相差無幾,比如儘可能的保持小的JS體積和少的RTT(https://en.wikipedia.org/wiki...)。甚至關鍵的腳本和數據如果使用HTTP/2的服務器端推送,或者使用<link rel=preload>,我們還可以讓解析工作更早開始。另外,像PRPL這樣的技術也可以幫助我們加快頁面的初始化及其後續導航。

clipboard.png

但事情並不是這麼簡單。客戶端渲染的主要問題是,所需的JavaScript會隨着應用程序的增長而增長。隨着新的JavaScript庫、兼容組件和第三方代碼的添加,控制腳本的規模會變得格外困難——尤其是這些代碼和庫都經常都需要在頁面內容渲染之前被加載。所以對於那些腳本規模很大的應用,應該優先考慮使用代碼拆分的方案。特別的,對於懶加載的JavaScript,要確保只在有需要時才加載必要的代碼。而對於只有很少或者沒有什麼交互的應用,服務器渲染是一個可擴展性更好的方案。

如果你想構建SPA(單頁)應用,使用app shell緩存頁面中交互的核心組件會給你很大的幫助。如果結合PWA的Service Work技術,還可以有效提高頁面重複訪問時的性能。

7. 通過rehydration將服務器渲染和客戶端渲染相結合

通常稱爲同構渲染或者直接簡單地稱爲"SSR",這種方式嘗試在客戶端渲染和服務端渲染之間尋找平衡,希望能夠減少兩者的弊端。

頁面導航導致跳轉或刷新時,服務器會輸出頁面的HTML文檔,並把該頁面所需要的javascript和(用於渲染的)數據內聯到文檔一起輸出。如果實現得當,這種方式確實可以像服務端渲染那樣實現較快的首次內容繪製(First Contentful Paint),之後客戶端會通過一種叫rehydration的技術繼續(在客戶端)渲染。這是個新穎的技術,但它會引起比較大的性能問題。

使用reydration技術進行服務端渲染的主要問題在於它會對可交互時間(Time To Interactive)有明顯的負面影響,儘管它縮短了首次繪製時間(First Paint)。服務端渲染的頁面往往讓人感覺已經加載完畢並可以開始交互了,但實際上只有等到客戶端的js腳本執行並完成DOM事件綁定才能響應用戶的交互(例如用戶的輸入行爲)。在一些手機終端,這個過程會耗費幾秒甚至幾分鐘的時間。

也許你自己也經歷過這樣的場景:一個頁面看起來已經加載完成了,但是在頁面執行點擊或者輕觸的動作,結果卻什麼也沒發生。這很快變得令人沮喪......“爲什麼(頁面)沒有反應?爲什麼我不能滾動?”

Rehydration問題:重複

Rehydration的問題不止於此,通常比因js導致的交互延遲更糟糕。爲了讓客戶端js能夠準確地渲染,而不用重新向服務器請求渲染所需的數據,目前服務端渲染通常會把UI所需的數據序列化並內聯到HTML文檔的script標籤裏。最終的HTML文檔包含了更高層面的重複:

clipboard.png

可以看到,對於頁面導航請求,服務器會返回了相應的UI描述(HTML),但它同樣返回了渲染UI所需的數據。同時,客戶端腳本同樣包括了UI的描述(譯者注:前端渲染需要包含對UI的描述,例如JSX),以便在客戶端繼續渲染。只有當bundle.js完成下載和執行後,UI才進入可交互狀態。

從使用rehydration方案的一些真實網站蒐集到的性能數據來看,該方案是極度不推薦的。究其原因,還是回到用戶體驗上:這種方式很容易讓用戶停留在“神祕的峽谷”之中。(譯者注:即界面可見但不可交互的狀態)

clipboard.png

儘管如此,外界對rehydration方案還是有些許期待的。簡單來說,使用服務端渲染時,只對需要高度緩存的內容纔會降低首字節時間(TTFB),得到和預渲染(prerendering)類似的結果。

在未來,rehydtration方案可能會逐漸地、或者部分地被應用,併成爲服務端渲染的關鍵。

8. 流式服務端渲染和漸進式Rehydration

服務端渲染在過去幾年中有了長足的進展。

流式服務端渲染允許你以塊的形式發送HTML,同時瀏覽器端接收並逐一渲染,這種方案可以實現快速的FP和FCP。React提供了一個異步的、以流的方式傳輸的方式 renderToNodeStream,與同步的renderToString相比,它能夠更好處理服務器壓力。

漸進式Rehydration也值得關注,React團隊正在做一些有趣的探索(https://github.com/facebook/r...)。使用這種方法,服務器渲染隨着時間的推移被“啓動”去渲染應用的各個片段,而不是當前一次性渲染整個應用。這可以幫助減少使頁面交互所需的JavaScript,因爲這樣可以延遲頁面中低優先級展示內容的渲染(比如非首屏的內容),同時可以防止這些低優先級的渲染阻塞主線程。而且,這種方案也能避免帶Rehydration的服務端渲染的一個很大的陷阱:服務端渲染生成的DOM樹在瀏覽器端被銷燬然後被重建,這個問題大多數是因爲同步的客戶端渲染生成DOM樹所需要的初始數據還沒準備好。

局部Rehydration

局部rehydration被證明難以實現。這個方案是漸進式Rehydration的一個擴展,需要分析出相互獨立的片段(組件/視圖/樹)中哪些具有極少交互或者完全沒有交互的部分進行漸進式rehydrated。對於這些近乎靜態的部分,相應JavaScript代碼會被改造成惰性的引用,從而將它們對客戶端的影響降低到近乎爲0。局部hydration爲緩存帶來了一定挑戰,客戶端導航意味着我們不能假設應用程序的惰性部分即服務器渲染生成的HTML是可用的,除非頁面完全加載完。

Trisomorphic渲染
如果Service Worker是你的一個選項,“trisomorphic”渲染也是一個有趣的點子。利用這項技術,你可以利用流式服務端渲染生成初始的或不依賴JS的部分,然後在Service Worker install後利用它渲染生成html。這種方案能使緩存的組件和模板保持實時而且還支持SPA類型的應用,在同一會話中根據不同的導航渲染不同的視圖。這種方法在服務器端、客戶端和Service Worker中可以複用模板和路由代碼。

clipboard.png

9. SEO

在選擇在渲染方案,通常會考慮SEO。通常我們會選擇服務器渲染來應對爬蟲。爬蟲可能能理解JavaScript,但是它們有很多侷限性,我們需要重點關注一下他們是如何渲染的。雖然客戶端渲染可以工作,但是一般都沒有做測試等工作,如果您的應用依賴於客戶端渲染,動態渲染是您值得考慮的一個選項,具體可以參考https://developers.google.com...

如果有疑問,Mobile Friendly Test可以測試您選擇的方法是否符合預期,它可以顯示頁面在爬蟲中的顯示方式、序列化的HTML內容(JavaScript執行之後)以及渲染過程中出現的任何錯誤。

Mobile Friendly Test地址如下:
https://search.google.com/tes...

clipboard.png

10. 總結

當決定用什麼方式渲染的時候,要知道我們即將遇到的瓶頸是什麼。採用客戶端渲染還是服務端渲染將決定你90%的架構設計。一個完美的解決方案通常服務端發送html跟最小的js來完成交互。以下是服務端-客戶端渲染的一個總結圖:

clipboard.png

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章