在網路上算繪

發布日期:2019 年 2 月 6 日,上次更新日期:2026 年 1 月 5 日

網頁開發人員必須做出的一項核心決策,就是在應用程式中實作邏輯和算繪的位置。這可能很困難,因為建立網站的方式非常多。

我們在 Chrome 方面與大型網站合作多年,因此對這個領域有深入瞭解。一般來說,我們鼓勵開發人員考慮使用伺服器端轉譯或靜態轉譯,而非完整重新 Hydration 的方法。

如要進一步瞭解我們在做出這項決策時選擇的架構,我們需要一致的術語,以及每種方法的共用框架。然後,您就能從網頁效能的角度,更妥善地評估每種算繪方法的取捨。

術語

首先,我們要定義一些術語。

轉譯

伺服器端算繪 (SSR)
在伺服器上轉譯應用程式,將 HTML (而非 JavaScript) 傳送至用戶端。
用戶端算繪 (CSR)
在瀏覽器中算繪應用程式,並使用 JavaScript 修改 DOM。
預先算繪
在建構期間執行用戶端應用程式,以靜態 HTML 形式擷取初始狀態。注意:此處的「預先算繪」與瀏覽器預先算繪未來導覽不同。
飲水量
執行用戶端指令碼,在伺服器算繪的 HTML 中加入應用程式狀態和互動功能。水合作用會假設 DOM 不會變更。
重新佈建
雖然通常與 Hydration 同義,但 Rehydration 是指定期使用最新狀態更新 DOM,包括初始 Hydration 之後。

效能

Time to First Byte (TTFB)
點選連結到新網頁載入第一個內容位元之間的時間。
First Contentful Paint (FCP)
要求內容 (文章內文等) 顯示的時間。
Interaction to Next Paint (INP)
這項代表性指標可評估網頁是否能持續快速回應使用者輸入內容。
總封鎖時間 (TBT)
INP 的代理指標,可計算載入網頁期間主執行緒遭到封鎖的時間長度。

伺服器端算繪

伺服器端轉譯會在伺服器上產生網頁的完整 HTML,以回應導覽。這樣一來,由於轉譯器會在瀏覽器收到回應前處理這些作業,因此可避免在用戶端額外往返擷取資料和範本。

伺服器端轉譯通常會產生快速的 FCP。在伺服器上執行網頁邏輯和算繪作業,可避免將大量 JavaScript 傳送至用戶端。這有助於減少網頁的 TBT,進而降低 INP,因為載入網頁期間不會經常阻斷主執行緒。主要執行緒遭到封鎖的次數越少,使用者互動就越有機會盡快執行。

這是合理的,因為使用伺服器端算繪時,您實際上只會將文字和連結傳送至使用者的瀏覽器。這個做法適用於各種裝置和網路狀況,並可進行有趣的瀏覽器最佳化,例如串流文件剖析。

圖表:顯示伺服器端轉譯和 JavaScript 執行程序對首次內容繪製 (FCP) 和互動時間 (TTI) 的影響。
使用伺服器端算繪的 FCP 和 TTI。

採用伺服器端算繪後,使用者就不必等待受 CPU 限制的 JavaScript 執行完畢,才能使用網站。即使無法避免使用第三方 JavaScript,您還是可以透過伺服器端算繪,減少自己的第一方 JavaScript 費用,為其餘部分爭取更多預算。不過,這種做法可能會有一個缺點:在伺服器上產生網頁需要時間,可能會增加網頁的 TTFB。

伺服器端算繪是否足以支援應用程式,主要取決於您要建構的體驗類型。長期以來,伺服器端轉譯與用戶端轉譯的正確應用方式一直備受爭議,但您隨時可以選擇對部分網頁使用伺服器端轉譯,對其他網頁則不使用。部分網站已採用混合式算繪技術,並獲得良好成效。 舉例來說,Netflix 會伺服器算繪相對靜態的到達網頁,同時prefetching互動密集型網頁的 JavaScript,讓這些較重的用戶端算繪網頁有較高的機會快速載入。

許多現代架構、程式庫和架構都能在用戶端和伺服器上算繪相同的應用程式。您可以使用這些技術進行伺服器端算繪。不過,在伺服器和用戶端上進行算繪的架構,屬於另一類解決方案,效能特徵和取捨考量大不相同。React 使用者可以運用伺服器 DOM API,或以這些 API 為基礎建構的解決方案 (例如 Next.js) 進行伺服器端算繪。Vue 使用者可以參閱 Vue 的伺服器端轉譯指南或 Nuxt。Angular 具有 Universal。

不過,最熱門的解決方案會使用某種形式的補水,因此請注意工具採用的方法。

靜態算繪

靜態算繪會在建構時間發生。只要限制網頁上的用戶端 JavaScript 數量,這種做法就能快速顯示 FCP,並降低 TBT 和 INP。與伺服器端算繪不同,由於網頁的 HTML 不必在伺服器上動態產生,因此這項技術也能持續實現快速的 TTFB。一般來說,靜態算繪是指預先為每個網址產生獨立的 HTML 檔案。預先產生 HTML 回應後,您可以將靜態算繪部署至多個 CDN,充分運用邊緣快取。

圖表:顯示靜態轉譯和選用 JavaScript 執行作業如何影響首次內容繪製 (FCP) 和互動時間 (TTI)。
使用靜態算繪的 FCP 和 TTI。

靜態算繪解決方案的形狀和大小不盡相同。Gatsby 等工具的設計宗旨,是讓開發人員覺得應用程式是動態算繪,而不是在建構步驟中產生。11ty、Jekyll 和 Metalsmith 等靜態網站產生工具採用靜態本質,提供更以範本為導向的方法。

靜態算繪的缺點之一,是必須為每個可能的網址產生個別的 HTML 檔案。如果您需要預先預測這些網址,或是網站有大量不重複的網頁,這項作業可能相當困難,甚至無法完成。

React 使用者可能熟悉 Gatsby、Next.js 靜態匯出或 Navi,這些工具都能輕鬆從元件建立網頁。不過,靜態算繪和預先算繪的行為不同:靜態算繪的網頁是互動式網頁,不需要執行大量用戶端 JavaScript;預先算繪則可改善單頁應用程式的 FCP,但必須在用戶端啟動,才能讓網頁真正具備互動性。

如果不確定特定解決方案是靜態算繪還是預先算繪, 請嘗試停用 JavaScript,然後載入要測試的網頁。對於靜態算繪頁面,大多數互動功能仍可在沒有 JavaScript 的情況下運作。預先算繪的網頁可能仍具備一些基本功能,例如停用 JavaScript 的連結,但網頁的大部分內容都是靜態。

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

伺服器端算繪與靜態算繪

伺服器端算繪並非萬靈丹,因為動態特性可能會導致龐大的運算作業負擔。許多伺服器端算繪解決方案不會提早排清,導致 TTFB 延遲,或傳送的資料量加倍 (例如用戶端 JavaScript 使用的內嵌狀態)。在 React 中,renderToString() 可能是同步單一執行緒,因此速度較慢。新版 React 伺服器 DOM API 支援串流,可更快將 HTML 回應的初始部分傳送至瀏覽器,同時在伺服器上產生其餘部分。

「正確」取得伺服器端算繪結果可能需要尋找或建構元件快取解決方案、管理記憶體耗用量、使用記憶化技術,以及處理其他問題。您經常會處理或重建同一個應用程式兩次,一次在用戶端,一次在伺服器。伺服器端轉譯可更快顯示內容,但這不一定能減少您的工作量。如果伺服器產生的 HTML 回應傳送至用戶端後,您在用戶端仍有大量工作,網站的 TBT 和 INP 仍可能較高。

伺服器端轉譯會為每個網址依需求產生 HTML,但速度可能比單純提供靜態轉譯內容慢。如果您願意多花點力氣,伺服器端算繪加上 HTML 快取,就能大幅縮短伺服器算繪時間。伺服器端轉譯的優點是能夠擷取更多「即時」資料,並回應比靜態轉譯更完整的要求。需要個人化的網頁是具體範例,說明哪些類型的要求不適合靜態算繪。

建構 PWA 時,伺服器端算繪也會帶來有趣的決策。使用全頁 Service Worker 快取,還是伺服器算繪個別內容片段比較好?

用戶端算繪

用戶端轉譯是指使用 JavaScript 直接在瀏覽器中轉譯網頁。所有邏輯、資料擷取、範本和路徑都會在用戶端處理,而不是在伺服器上處理。實際結果是伺服器會將更多資料傳遞至使用者的裝置,而這會帶來一連串的取捨。

用戶端算繪可能難以製作,且難以維持行動裝置的效能。 只要稍加努力,維持嚴格的 JavaScript 預算,並盡可能減少往返次數來提供價值,您就能讓用戶端算繪幾乎複製純伺服器端算繪的效能。使用 <link rel=preload> 傳送重要指令碼和資料,可加快剖析器為您工作的速度。我們也建議考慮使用 PRPL 等模式,確保初始和後續導覽都能立即完成。

圖表:顯示用戶端算繪對首次內容繪製 (FCP) 和互動時間 (TTI) 的影響。
使用用戶端算繪的 FCP 和 TTI。

用戶端算繪的主要缺點是,隨著應用程式成長,所需的 JavaScript 量也會增加,進而影響網頁的 INP。如果加入新的 JavaScript 程式庫、polyfill 和第三方程式碼,情況會變得更加困難,因為這些項目會爭奪處理能力,而且通常必須先處理完畢,網頁內容才能顯示。

如果體驗使用用戶端算繪,並依賴大型 JavaScript 組合,建議考慮積極分割程式碼,以降低載入網頁期間的 TBT 和 INP,並延遲載入 JavaScript,只在需要時提供使用者需要的內容。對於互動程度低或沒有互動的體驗,伺服器端算繪可做為這些問題的更具延展性解決方案。

對於建構單頁應用程式的人員來說,找出大多數網頁共用的使用者介面核心部分,有助於套用應用程式殼層快取技術。搭配服務工作人員使用,可大幅提升重複造訪時的感知效能,因為網頁可以從 CacheStorage 快速載入應用程式外殼 HTML 和依附元件。

重新佈建會結合伺服器端和用戶端算繪

Hydration 是一種方法,可同時執行用戶端和伺服器端算繪,減少兩者之間的取捨。導覽要求 (例如完整網頁載入或重新載入) 由伺服器處理,伺服器會將應用程式轉譯為 HTML。然後,用於算繪的 JavaScript 和資料會嵌入產生的文件中。如果謹慎執行,就能像伺服器端轉譯一樣快速達成 FCP,然後在用戶端再次轉譯「接手」。

這是有效的解決方案,但可能會造成效能大幅下降。

使用重新水合的伺服器端算繪主要缺點是,即使改善了 FCP,仍可能對 TBT 和 INP 造成重大負面影響。伺服器端算繪的網頁可能看起來已載入並可互動,但實際上要等到元件的用戶端指令碼執行完畢,且事件處理常式已附加,才能回應輸入內容。在行動裝置上,這可能需要幾分鐘,造成使用者困惑和挫折。

重新佈建問題:買一送一

為了讓用戶端 JavaScript 準確接手伺服器的工作,而不必重新要求伺服器用來轉譯 HTML 的所有資料,大多數伺服器端轉譯解決方案都會將 UI 資料依附元件的回應序列化為文件中的指令碼標記。因為這會複製大量 HTML,重新補水可能會導致更多問題,而不只是延遲互動。

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

伺服器會傳回應用程式 UI 的說明,以回應導覽要求,但也會傳回用於撰寫該 UI 的來源資料,以及 UI 實作的完整副本,然後在用戶端啟動。bundle.js 完成載入及執行作業後,使用者介面才會開始互動。

從使用伺服器端算繪和重新水合的實際網站收集到的成效指標顯示,這很少是最佳選項。最重要的原因在於,如果網頁看起來已準備就緒,但互動式功能都無法運作,就會影響使用者體驗。

用戶端算繪對 TTI 的負面影響。

使用重新補水功能進行伺服器端轉譯,可望解決這個問題。短期內,只對高度可快取的內容使用伺服器端算繪,即可減少 TTFB,產生與預先算繪類似的結果。逐步、漸進或部分重新補水,可能是日後讓這項技術更具可行性的關鍵。

串流伺服器端轉譯,並逐步重新補水

過去幾年來,伺服器端算繪技術有許多發展。

串流伺服器端算繪 可讓您以區塊形式傳送 HTML,瀏覽器收到後即可逐步算繪。這可讓使用者更快取得標記,加快 FCP。在 React 中,與同步 renderToString() 相比,renderToPipeableStream() 中的串流為非同步,表示可妥善處理反向壓力。

漸進式重新補水也值得考慮 (React 已實作這項功能)。採用這種做法時,伺服器算繪應用程式的個別部分會隨著時間「啟動」,而不是像目前常見的做法一樣,一次初始化整個應用程式。這項功能可延後網頁中低優先順序部分的用戶端升級,避免阻斷主執行緒,讓使用者在啟動互動後能更快進行互動,因此有助於減少網頁互動所需的 JavaScript 數量。

漸進式重新補水也有助於避免最常見的伺服器端算繪重新補水陷阱:伺服器算繪的 DOM 樹狀結構遭到毀損,然後立即重建,最常見的原因是初始同步用戶端算繪需要尚未準備就緒的資料,通常是尚未解析的 Promise。

部分重新佈建

部分重新佈建 (rehydration) 功能實作不易,這種做法是漸進式重新補水的延伸功能,可分析網頁的個別部分 (元件、檢視區塊或樹狀結構),並找出互動性低或沒有反應的部分。對於這些大多為靜態的部分,對應的 JavaScript 程式碼會轉換為惰性參照和裝飾功能,將用戶端足跡縮減至近乎零。

部分重新水合方法本身也有問題,而且會造成妥協。這會對快取造成一些有趣的挑戰,而且用戶端導覽表示我們無法假設應用程式中非作用中部分的伺服器轉譯 HTML 可用,而不必載入整個網頁。

三形算繪

如果可以採用服務工作人員,請考慮使用同構算繪。這項技術可讓您在初始或非 JavaScript 導覽時,使用串流伺服器端轉譯,然後在安裝服務工作站後,讓服務工作站接手導覽的 HTML 轉譯作業。這項功能可讓快取元件和範本保持最新狀態,並在同一工作階段中啟用 SPA 樣式的導覽,以便轉譯新檢視畫面。如果伺服器、用戶端網頁和 Service Worker 之間可以共用相同的範本和路由程式碼,這個做法就非常實用。

同構算繪,示範瀏覽器和 Service Worker 與伺服器通訊。

搜尋引擎最佳化注意事項

選擇網頁算繪策略時,團隊通常會考量搜尋引擎最佳化 (SEO) 的影響。伺服器端算繪是熱門選項,可提供「完整外觀」的體驗,供檢索器解讀。檢索器可以解讀 JavaScript,但通常無法完整呈現。用戶端算繪雖然可行,但通常需要額外測試,且會增加作業負擔。最近,如果您的架構高度依賴用戶端 JavaScript,動態轉譯也成為值得考慮的選項。

結論

決定採用哪種算繪方法時,請先評估並瞭解瓶頸所在。請考慮是否可透過靜態轉譯或伺服器端轉譯,您大可只傳送 HTML,並搭配最少的 JavaScript,讓使用者獲得互動式體驗。這張實用的資訊圖表顯示了伺服器與用戶端之間的關係:

算繪選項及其優缺點。

抵免額

感謝所有提供評論和靈感的使用者:

Jeffrey Posnick、Houssein Djirdeh、Shubhie Panicker、 Chris Harrelson 和 Sebastian Markbåge。