在網路上算繪

Addy Osmani
Addy Osmani

網頁開發人員必須決定 在應用程式中實作邏輯和算繪的一點是核心決策這項工作可能並不容易 因為建立網站的方法很多

過去幾年來,我們在 Chrome 與大型網站討論的成果,正是我們對這個空間的理解。廣泛來說,我們鼓勵開發人員考慮透過完整的補水方法,考慮伺服器端算繪或靜態算繪作業。

為了深入瞭解我們做出這項決策時選擇的架構,我們需要對每種方法有充分的瞭解,並在討論時採用一致的術語。從轉譯方式之間的差異有助於瞭解網頁算繪與網頁效能之間的取捨。

術語

轉譯

伺服器端轉譯 (SSR)
在伺服器上將用戶端或通用應用程式算繪為 HTML。
用戶端轉譯 (CSR)
使用 JavaScript 在瀏覽器中轉譯應用程式,並利用 JavaScript 修改 DOM。
補充水分
在用戶端上「啟動」JavaScript 檢視畫面,以便重複使用伺服器算繪 HTML 的 DOM 樹狀結構和資料。
預先算繪
在建構期間執行用戶端應用程式,以擷取靜態 HTML 的初始狀態。

效能

首次位元組時間 (TTFB)
從點選連結到在新頁面載入內容第一個位元組之間的時間。
首次顯示內容所需時間 (FCP)
顯示要求內容 (文章內文等) 的時間。
與下一個顯示的內容 (INP) 互動
這個代表性指標可用於評估網頁是否持續快速回應使用者輸入內容。
總封鎖時間 (TBT)
INP 的 Proxy 指標,計算網頁載入期間主要執行緒遭到封鎖的時間長度。

伺服器端算繪

伺服器端轉譯作業會為伺服器中的網頁產生完整的 HTML,以回應導覽。這樣可以避免在用戶端上擷取資料及建立範本所需的來回行程,因為轉譯器會在瀏覽器收到回應前進行處理。

伺服器端算繪通常會產生快速的 FCP。在伺服器上執行頁面邏輯並進行轉譯,可避免將大量 JavaScript 傳送至用戶端。這有助於減少網頁的 TBT,進而導致 INP 較低,因為在頁面載入期間,主要執行緒沒有經常遭到封鎖。如果主執行緒的封鎖頻率較低,使用者互動的機會就越多。這是很合理的,因為在伺服器端轉譯,您只需要傳送文字和連結給使用者的瀏覽器。此方法適用於各種裝置和網路狀況,並可開啟有趣的瀏覽器最佳化功能,例如串流文件剖析。

這張圖表顯示影響 FCP 和 TTI 的伺服器端轉譯和 JS 執行作業。
FCP 和 TTI 支援伺服器端轉譯。

如果採用伺服器端轉譯,使用者在使用您的網站前,就不太可能等待受 CPU 限制的 JavaScript 執行。即使您無法避免避免使用第三方 JS,使用伺服器端轉譯功能來減少自己的第一方 JavaScript 費用,可以提供更多預算。不過,這種方法有一項潛在的取捨:在伺服器上產生頁面需要時間,這可能會增加網頁的 TTFB。

至於伺服器端轉譯作業是否足以滿足應用程式的需求,主要取決於要建構的使用體驗。對於伺服器端轉譯與用戶端轉譯的正確應用程式,仍有長期的討論,但您仍然可以選擇針對某些網頁啟用伺服器端轉譯,而不對其他網頁採用。有些網站成功採用混合式轉譯技術。舉例來說,Netflix 伺服器轉譯相對靜態的到達網頁,同時針對大量互動網頁預先擷取 JS,因此較能快速載入用戶端轉譯的 JS。

許多現代的架構、程式庫和架構都可讓您在用戶端和伺服器上轉譯相同的應用程式。您可以運用這些技術進行伺服器端轉譯。但是,在伺服器「和」用戶端上進行轉譯的架構,都有其專屬的解決方案類別,其效能特性和優缺點截然不同。React 使用者可以使用伺服器 DOM API,或以 Next.js 等為基礎建構的解決方案進行伺服器端轉譯。Vue 使用者可以參閱 Vue 的伺服器端轉譯指南Nuxt。Angular 提供「Universal」(通用)。不過,最常見的解決方案採用某種補水方式,因此請留意您的工具使用的方法。

靜態算繪

建構期間會發生靜態轉譯作業。這個方法提供快速的 FCP,以及較低的 TBT 和 INP,只要限制網頁上的用戶端 JS 數量即可。與伺服器端算繪不同,網頁的 HTML 不必在伺服器上動態產生,因此也能持續快速的 TTFB。一般而言,靜態轉譯是指預先為每個網址產生獨立的 HTML 檔案。透過預先產生的 HTML 回應,您可以將靜態轉譯部署至多個 CDN,以充分運用邊緣快取。

這張圖表顯示影響 FCP 和 TTI 的靜態轉譯和選用的 JS 執行。
FCP 和 TTI 支援靜態轉譯。

靜態算繪的解決方案有多種形狀和大小。Gatsby 這類工具的設計宗旨是讓開發人員感覺應用程式是以動態方式轉譯,而非做為建構步驟。靜態網站產生工具 (例如 11tyJekyllMetalsmith) 採用靜態的特性,提供更以範本為導向的做法。

靜態轉譯的缺點之一是,必須為每個可能網址產生個別 HTML 檔案。當您無法事先預測這些網址會有怎樣的內容,或是含有大量不重複網頁的網站時,這個做法可能並不容易,甚至完全無法運作。

React 使用者可能熟悉 Gatsby、Next.js 靜態匯出Navi,這些都有助於透過元件建立頁面。不過,靜態轉譯和預先算繪的運作方式不同:靜態轉譯頁面不需要執行大量用戶端 JavaScript,即可進行互動,預先算繪則可改善單頁應用程式的 FCP,而預先算繪功能可以在用戶端啟動,才能使網頁具有互動性。

如果您不確定指定的解決方案是靜態轉譯或預先轉譯,請嘗試停用 JavaScript 並載入要測試的頁面。如果是靜態轉譯的網頁,大部分的互動功能仍可在沒有 JavaScript 的情況下使用。預先算繪頁面可能仍有一些基本功能 (例如停用 JavaScript 的連結),但大部分網頁沒有宣告。

另一項實用的測試是使用 Chrome 開發人員工具中的網路節流,查看網頁互動前的 JavaScript 下載量。預先算繪通常需要更多的 JavaScript 才能變成互動式,而且 JavaScript 通常會比靜態轉譯中使用的漸進式強化方法更為複雜。

伺服器端算繪與靜態算繪

伺服器端轉譯並非一體適用,因為其動態性質可能會產生高額的運算負擔,因此也不成問題。許多伺服器端轉譯解決方案不會提前清除、延遲 TTFB 或傳送兩倍傳送的資料 (例如在用戶端上 JavaScript 使用的內嵌狀態)。在 React 中,renderToString() 是同步和單一執行緒的處理速度。新版 React 伺服器 DOM API 支援串流功能,這類 API 可以在伺服器產生 HTML 回應的初始部分,更快取得瀏覽器的初始部分。

使伺服器端轉譯作業「正確」可能涉及尋找或建構元件快取的解決方案、管理記憶體用量、使用記憶技術,以及其他問題。您通常會在用戶端和伺服器上一次處理或重新建構相同的應用程式。較快的伺服器端算繪作業不一定能減輕您的負擔。如果您在用戶端產生了伺服器產生的 HTML 回應之後,對用戶端執行許多工作,這仍可能為網站帶來更高的 TBT 和 INP。

伺服器端算繪作業會為每個網址產生隨選 HTML,但可能比提供靜態轉譯內容的速度慢。如果能額外加裝,伺服器端算繪和 HTML 快取功能可能會大幅縮短伺服器轉譯時間。在伺服器端算繪之外,與靜態轉譯相比,能夠提取更多「即時」資料,並回應更完整的要求組合。需要個人化的網頁是具體的範例,不適用於靜態轉譯。

建構 PWA 時,伺服器端轉譯功能也能帶來有趣的決策:使用全頁服務工作站快取,或只使用伺服器轉譯個別內容片段是更好的做法嗎?

用戶端轉譯

用戶端轉譯是指使用 JavaScript 直接在瀏覽器中轉譯網頁。所有邏輯、資料擷取、範本和轉送都會在用戶端 (而不是伺服器上) 處理。有效的結果是將更多資料從伺服器傳遞至使用者的裝置,進而產生自己的取捨。

針對行動裝置,用戶端算繪並不容易,而且維持快速。只要多點工作就能精準管理 JavaScript 預算,並盡可能減少往返作業的價值,進而實現用戶端轉譯,幾乎複製純伺服器端轉譯的效能。您可以利用 <link rel=preload> 提供重要指令碼和資料,讓剖析器更快代您處理工作。此外,我們也建議使用 PRPL 等模式,確保初始和後續導覽操作都能順暢運作。

這張圖表顯示影響 FCP 和 TTI 的用戶端轉譯。
FCP 和 TTI 支援用戶端轉譯。

而用戶端轉譯的主要缺點是,隨著應用程式增長,所需的 JavaScript 數量通常會隨之增加,這可能會影響網頁的 INP。加入新的 JavaScript 程式庫、polyfill 和第三方程式碼會競逐處理能力,而且通常在可以轉譯頁面內容之前便需要處理,因此會變得特別困難。

如果體驗使用用戶端轉譯並仰賴大型 JavaScript 套件,則應考慮採用積極的程式碼分割機制,在頁面載入期間降低 TBT 和 INP,以及延遲載入 JavaScript,只在需要時滿足使用者需求。對於互動性很少或沒有互動的情況,伺服器端轉譯代表這些問題的擴充性較高。

對於建構單頁應用程式的人而言,識別大部分頁面共用的使用者介面核心部分,可讓您套用應用程式殼層快取技術。結合 Service Worker 後,即可大幅提高重複造訪的感知效能,因為頁面可以快速從 CacheStorage 載入其應用程式殼層 HTML 和依附元件。

脫水結合了伺服器端和用戶端轉譯

「補充」方法是嘗試透過同時執行這兩項操作,在用戶端和伺服器端轉譯之間取得平衡。載入或重新載入頁面等導覽要求是由伺服器處理,會將應用程式算繪成 HTML,然後將用於轉譯的 JavaScript 和資料嵌入至產生的文件中。仔細觀察後,系統會執行類似伺服器端轉譯的快速 FCP,然後在用戶端再次算繪,藉此「加速」並「加速」。這個方法雖然有效,但會大幅影響效能。

使用補水進行伺服器端轉譯的主要缺點是,即使可以改善 FCP,仍可能會對 TBT 和 INP 產生重大負面影響。伺服器端轉譯的網頁似乎可以載入及互動,但要等到元件的用戶端指令碼執行並附加事件處理常式後,才能實際回應輸入內容。在行動裝置上,這可能需要數分鐘、令人困惑又不悅使用者。

補水問題:一個價格為二的應用程式

為了讓用戶端 JavaScript 準確地「接續」伺服器進度,而不重新要求伺服器轉譯其 HTML 的所有資料,大多數的伺服器端轉譯解決方案都會將 UI 資料依附元件的回應序列化為文件中的指令碼標記。因為這樣會複製大量的 HTML,因此脫水會造成更多問題,不僅僅是延遲互動。

包含序列化 UI、內嵌資料和 bundle.js 指令碼的 HTML 文件
HTML 文件中有重複的程式碼。

伺服器會傳回應用程式 UI 的說明,以回應導覽要求,但也會傳回構成 UI 時使用的來源資料,以及 UI 實作的完整副本,接著在用戶端上啟動。待 bundle.js 完成載入和執行作業後,UI 才會設為互動式。

透過伺服器端轉譯和補充處理從實際網站收集的效能指標,也指出它很少是最佳選項。最重要的原因是,網頁在頁面就緒,但沒有任何互動功能正常運作時,對使用者體驗的影響。

這張圖表顯示用戶端轉譯對 TTI 造成負面影響。
用戶端算繪對 TTI 的影響。

不過,我們仍希望在伺服器端進行補充處理。短期內,僅針對高可快取的內容使用伺服器端算繪功能可以減少 TTTFB,產生與預先算繪類似的結果。如果想讓這項技術日後更容易運作,關鍵就在於遞增或逐步補充水分。

串流伺服器端算繪和補充水分

近幾年來,伺服器端算繪有許多開發工作。

串流伺服器端轉譯可讓您透過區塊傳送 HTML,讓瀏覽器在收到時逐步轉譯。這麼做可以更快為您的使用者加上標記,加快 FCP 的速度。在 React 中,與同步 renderToString() 相比,在 renderToPipeableStream() 中非同步的串流作業代表能妥善處理背壓。

此外,您也可以考慮循序漸進的補充水分,而 React 已導入了這項功能。透過這種方式,伺服器算繪應用程式的個別部分會隨著時間「啟動」,而不是當下一次初始化整個應用程式的常見做法。這有助於減少網頁互動所需的 JavaScript 數量,因為這項功能可讓您延遲用戶端升級網頁的低優先順序部分,以免封鎖主執行緒,讓使用者在啟動後更快發生互動。

漸進式補充水也有助於避免發生在伺服器端轉譯修正問題之一,因為由伺服器轉譯的 DOM 樹狀結構會遭到刪除並立即重建,這通常是因為初始同步的用戶端轉譯需要尚未準備就緒的資料,而 Promise 通常尚未解析。

部分補水

事實證明,部分分水已難以實施。這是一種循序漸進的補充水,可分析網頁的個別部分 (元件、檢視畫面或樹狀結構),並找出互動次數較少或沒有重複使用的部分。接著,針對這些大部分都是靜態的部分,對應的 JavaScript 程式碼會轉換成間接參照和裝飾功能,進而減少將用戶端佔用至幾乎零的用戶端空間。

部分水份攝取量其實有自己的問題和入侵。這在快取方面帶來一些有趣的挑戰,而用戶端導覽也意味著,在不載入完整頁面的情況下,我們無法假設伺服器轉譯的 HTML 用於應用程式的插入部分。

三角性算繪

如果您採用服務工作處理程序,請考慮採用三態性算繪。這項技術可讓您針對初始或非 JavaScript 導覽使用串流伺服器端算繪,然後讓服務工作工作站在安裝 HTML 後處理 HTML 進行導覽。這可讓快取元件和範本保持在最新狀態,並啟用 SPA 樣式導覽功能,以便在同一工作階段中算繪新的檢視畫面。當伺服器、用戶端頁面與服務工作站之間共用相同的範本和轉送程式碼時,這個方法就非常實用。

顯示瀏覽器和服務 Worker 與伺服器通訊的圖表,圖。
三角性算繪運作方式圖表。

搜尋引擎最佳化 (SEO) 注意事項

選擇網頁轉譯策略時,團隊通常會考量 SEO 的影響。伺服器端轉譯是讓檢索器能夠解讀「完整」體驗的熱門選擇。檢索器可以解讀 JavaScript,但轉譯方式經常有一些限制。用戶端轉譯雖然可以運作,但通常需要額外的測試和負擔。近來不久,動態轉譯也是一項值得考慮的選項,如果您的架構大量仰賴用戶端 JavaScript。

如有疑問,建議您使用行動裝置相容性測試工具,測試所選做法是否符合您的需求。它會以視覺化方式預覽 Google 檢索器對任何網頁顯示的樣子、在 JavaScript 執行後找到的序列化 HTML 內容,以及轉譯期間發生的任何錯誤。

行動裝置相容性測試 UI 的螢幕截圖。
行動裝置相容性測試 UI。

結論

在決定算繪方式時,請評估並瞭解您的瓶頸。請思考靜態轉譯或伺服器端轉譯能否為您帶來最大的助益。在大部分的情況下,僅透過極少的 JavaScript 傳送 HTML 就能獲得互動體驗。以下提供一個便利的資訊圖表 顯示伺服器用戶端的頻譜:

顯示本文所述選項類型的資訊圖表。
顯示選項及其優缺點。

抵免額

感謝大家提供的評論和靈感:

Jeffrey Posnick、 Houssein Djirdeh、 Shubhie Panicker、Chris Harrelson 和 Sebastian Markb 時顯示