在網路上算繪

我們應該在應用程式中的何處實作邏輯和算繪?我們應該使用伺服器端轉譯嗎?補充水分呢?讓我們來尋找答案吧!

阿迪奧斯馬尼
Addy Osmani

開發人員經常面臨會影響應用程式整體架構的決定。網頁開發人員必須在應用程式中實作邏輯和算繪,是網頁開發人員必須做出的其中一項核心決策。由於建立網站的方法有很多種,這一點並不容易。

過去幾年來,我們與大型網站溝通的成果來自於 Chrome 對這類環境的理解。廣泛來說,我們鼓勵開發人員考慮採用完整的補充方法,考慮在伺服器端轉譯或靜態算繪。

為了進一步瞭解我們做出這項決策時選擇的架構,我們必須對各項方法有充分的瞭解,並在討論時採用一致的術語。這兩種方法的差異有助於從效能的視角,說明網路算繪作業的取捨。

術語

轉譯

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

效能

伺服器端算繪

伺服器端轉譯會為伺服器上的網頁產生完整的 HTML,以回應導覽。這樣會避免在用戶端上擷取資料及建立範本所需的額外往返時間,因為系統會在瀏覽器收到回應前處理這項作業。

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

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

有了伺服器端轉譯功能,使用者就不太可能等待 CPU 限制的 JavaScript 執行後,才能使用您的網站。即使是無法避免第三方 JS 的情況下,使用伺服器端算繪功能也能減少第一方 JavaScript 費用,這樣就能為剩餘的預算提供更高的預算。不過,這種做法有一種潛在的取捨:在伺服器上產生頁面需要時間,這可能會造成更高的 TTFB。

至於伺服器端轉譯作業是否足以滿足應用程式的需求,主要取決於您要建立何種體驗。長久以來,對於伺服器端轉譯與用戶端轉譯的應用方式,仍有許多疑惑,但請記住,您可以選擇針對部分網頁啟用伺服器端顯示功能。有些網站成功採用混合式轉譯技術。Netflix 伺服器會轉譯相對靜態的到達網頁,同時針對互動量較多的網頁預先擷取 JS,因此較能快速載入用戶端轉譯網頁的 JS。

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

靜態算繪

建構期間會發生靜態轉譯作業。這個方法提供快速的 FCP,以及較低的 TBT 和 INP (假設用戶端 JS 數量有限)。與伺服器端算繪不同之處在於,它也設法達到穩定快速的 TTFB,因為伺服器的 HTML 不需要在伺服器上動態產生。一般來說,靜態轉譯是指預先為每個網址產生獨立的 HTML 檔案。透過預先產生的 HTML 回應,靜態算繪可部署至多個 CDN,進而充分運用邊緣快取的優勢。

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

靜態算繪的解決方案有多種形狀和大小。Gatsby 這類工具的設計目的是讓開發人員可以享受到動態轉譯樂趣,而非像建構步驟一樣產生應用程式。靜態網站產生工具 (例如 11tyJekyllMetalsmith) 採用靜態模式,因此能提供更符合範本的做法。

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

React 使用者可能熟悉 Gatsby、Next.js 靜態匯出Navi,因此很適合使用元件編寫網頁。不過,請務必瞭解靜態轉譯和預先算繪之間的差別:靜態轉譯網頁不需要執行許多用戶端 JavaScript,即可進行互動,預先算繪則可改善單頁應用程式的 FCP,而預先算繪功能可以在用戶端上啟動,網頁才能達到真正的互動。

如果您不確定指定的解決方案是否為靜態轉譯或預先算繪,請嘗試停用 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 的用戶端轉譯。

而用戶端轉譯的主要缺點是,隨著應用程式增加,所需的 JavaScript 數量往往也會增加,這可能會對網頁的 INP 造成負面影響。增加新的 JavaScript 程式庫、polyfill 和第三方程式碼,這些程式碼會競逐處理能力,而且經常必須在網頁內容顯示前完成處理,使這種情況更加困難。

如果體驗必須使用大型 JavaScript 套件的用戶端轉譯功能,請考慮在頁面載入期間以積極的方式分割程式碼,以降低 TBT 和 INP,並採用延遲載入 JavaScript,也就是「只在您需要時提供必要內容」。對於互動性很少或完全沒有相關體驗,伺服器端轉譯功能代表這些問題的擴充性較高。

如果您是建構單頁應用程式的人,在識別大部分頁面所共用的使用者介面核心部分時,您可以應用應用程式殼層快取技術。結合 Service Worker 後,系統可快速地從 CacheStorage 載入應用程式殼層 HTML 及其依附元件,大幅提高重複造訪時的感知效能。

透過補水機制合併伺服器端轉譯和用戶端算繪作業

這個方法會嘗試在用戶端轉譯和伺服器端算繪作業之間,透過雙管齊下順暢執行。載入或重新載入網頁等導覽要求,是由將應用程式算繪為 HTML 的伺服器處理,然後用於轉譯的 JavaScript 和資料嵌入至產生的文件中。仔細思考之後,這個做法會像伺服器端轉譯一樣快速完成 FCP,然後利用名為 (re)hydration 的技術,在用戶端再次轉譯,以「加速」完成。這個方法雖然有效,但可能會有大幅缺點。

採用補水機制的伺服器端轉譯主要缺點是,即使可以改善 FCP,它可能會對 TBT 和 INP 產生重大負面影響。伺服器端轉譯的網頁可以欺騙性載入和互動,但在執行元件的用戶端指令碼且附加事件處理常式之前,無法實際回應輸入內容。在行動裝置上可能需要幾秒鐘或甚至幾分鐘的時間。

也許您已經親身體驗了這項功能,在看來頁面載入後,點擊或輕觸頁面可能還是沒有任何反應。這很快就會造成困擾,因為使用者可能想知道,在嘗試與網頁互動時,什麼都沒有發生。

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

由於 JavaScript 的輔助,恢復問題通常比延遲互動更嚴重。為了讓用戶端 JavaScript 準確「接續」伺服器,不必重新要求伺服器轉譯其 HTML 所用的所有資料,目前的伺服器端轉譯解決方案通常會將 UI 資料依附元件的回應序列化,做為指令碼標記。產生的 HTML 文件包含高層級的重複項目:

包含序列化 UI、內嵌資料和 bundle.js 指令碼的 HTML 文件

如您所見,伺服器會傳回應用程式 UI 的說明,以回應導航要求,但也會傳回用來組合該 UI 的來源資料,以及 UI 實作的完整副本,接著在用戶端啟動。只有在 bundle.js 載入和執行完畢之後,這個使用者介面才會設為互動式。

透過伺服器端顯示和解除補充功能,從實際網站收集到的成效指標,代表不建議使用。最終原因是使用者體驗取決於使用者體驗:最後很容易讓使用者留在「迷彩的山谷」,即使網頁「看起來」已就緒,在無法互動的情況下,仍無法與任何內容互動。

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

不過,我們仍希望在伺服器端進行補充處理。就短期而言,只針對可快取的內容使用伺服器端算繪,可能會減少 TTFB,產生與預先算繪類似的結果。如果想讓這項技術日後變得更可行,關鍵就是以「遞增」或部分方式補充水分。

串流伺服器端轉譯和漸進式補水

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

串流伺服器端轉譯功能可讓您分段傳送 HTML,瀏覽器可以在收到時逐步轉譯。這可以加快 FCP 速度,因為標記可以更快抵達使用者。在 React 中,[renderToPipeableStream()] 中的串流與同步 renderToString() 相比,代表能妥善處理背壓。

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

漸進式補充水源也有助於避免發生在伺服器端轉譯重水問題其中一個常見錯誤;由伺服器轉譯的 DOM 樹狀結構會刪除並立即重建,這多半是因為初始同步的用戶端算繪需要尚未準備就緒的資料,可能正在等待 Promise 的解析。

部分補水

事實證明,部分分水已難以實施。這種方法是循序漸進的補充水概念,其中會分析個別零件 (元件/檢視畫面/樹狀結構) 被逐步補充水分,並發現互動性較少或未重複使用的零件。接著,針對這些大部分是靜態的部分,對應的 JavaScript 程式碼會轉換成插入參照和裝飾功能,進而將用戶端足跡減少到將近零。

部分水份攝取量其實有自己的問題和入侵。這會對快取造成一些有趣的挑戰,而用戶端導覽意味著我們無法假設伺服器轉譯的 HTML 就應用程式的插入部分可在無需完整載入網頁的情況下使用。

三角性算繪

如果您可以使用「Service Worker」選項,可能也會需要「關聯」呈現效果。您可以採用這個方法的伺服器端算繪技術,進行初始/非 JavaScript 導覽,然後讓服務工作人員在安裝 HTML 後,繼續轉譯 HTML 以供瀏覽。這可讓快取元件和範本保持在最新狀態,並啟用 SPA 樣式的導覽功能,以便在同一工作階段中呈現新的檢視畫面。當伺服器、用戶端頁面和 Service Worker 之間共用相同的範本和轉送程式碼時,就非常適合使用這個方法。

顯示瀏覽器和服務 Worker 與伺服器通訊的圖表,圖。

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

團隊在選擇網路轉譯策略時,通常會將 SEO 的影響納入考量。系統通常會選擇伺服器端轉譯,藉此提供「功能完整的」體驗,讓檢索器能輕易解讀。檢索器可能可以解讀 JavaScript,但請留意其轉譯方式經常有一些限制。用戶端算繪可以正常運作,但通常必須進行額外測試和前置作業,才能順利進行。近年來,如果架構嚴重仰賴用戶端 JavaScript,動態轉譯也是值得考慮的選項。

如有疑問,建議您使用行動裝置相容性測試工具,測試所選做法是否達到預期目標。這項工具會以視覺化方式,預覽 Google 檢索器如何呈現任何網頁、找到的序列化 HTML 內容 (執行 JavaScript 後),以及轉譯時發生的所有錯誤。

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

總結

在決定算繪方式時,請評估並瞭解您的瓶頸。請思考靜態轉譯或伺服器端轉譯能否充分發揮這一點。您可以在大部分情況下透過極少 JavaScript 傳送 HTML 來取得互動體驗。以下提供簡易的資訊圖表,顯示伺服器與用戶端的強度:

顯示本文所述選項類型的資訊圖表。

抵免額

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

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