在網頁上轉譯

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

網頁程式開發人員必須做出的核心決策之一,是在應用程式中實作邏輯和轉譯的位置。由於建立網站的方式有很多種,因此這可能很困難。

過去幾年,我們在 Chrome 團隊與大型網站溝通,因此對這個領域有所瞭解。一般來說,我們建議開發人員考慮使用伺服器端轉譯或靜態轉譯,而非完整重新 Hydration 的做法。

為了更清楚瞭解我們在做出這項決定時所選擇的架構,我們需要一致的術語和每種方法的共用架構。接著,您就能從網頁效能的角度,更妥善地評估每種轉譯方法的優缺點。

術語

首先,我們會定義一些要使用的術語。

轉譯

伺服器端算繪 (SSR)
在伺服器上轉譯應用程式,將 HTML (而非 JavaScript) 傳送至用戶端。
用戶端轉譯 (CSR)
在瀏覽器中算繪應用程式,並使用 JavaScript 修改 DOM。
預先算繪
在建構時執行用戶端應用程式,以靜態 HTML 擷取初始狀態。請注意,這裡的「預先算繪」與瀏覽器預先算繪未來導覽不同。
飲水量
執行用戶端指令碼,將應用程式狀態和互動功能新增至伺服器轉譯的 HTML。Hydration 假設 DOM 不會變更。
重新佈建
雖然 rehydration 通常與 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,或以其為基礎建構的解決方案 (例如 Next.js),進行伺服器端轉譯。Vue 使用者可以參考 Vue 的伺服器端轉譯指南或 Nuxt。Angular 則有 Universal。

不過,大多數熱門解決方案都會使用某種形式的水合,因此請留意工具使用的方法。

靜態算繪

靜態轉譯會在建構時進行。只要限制網頁上的用戶端 JavaScript 數量,這種做法就能提供快速的 FCP,以及較低的 TBT 和 INP。與伺服器端轉譯不同,它也能達到一致的快速 TTFB,因為網頁的 HTML 不必在伺服器上動態產生。一般來說,靜態轉譯是指預先為每個網址產生個別的 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,只在使用者需要時提供所需內容。對於互動性極低或沒有互動性的體驗,伺服器端轉譯可提供更具擴充性的解決方案。

如果建構單頁應用程式,找出大多數頁面共用的使用者介面核心部分,即可套用應用程式殼層快取技術。搭配 Service Worker 使用,可大幅提升使用者再次造訪時的感知效能,因為網頁可以快速從 CacheStorage 載入應用程式殼層 HTML 和依附元件。

Rehydration 結合了伺服器端和用戶端算繪

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

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

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

復水問題:一個應用程式,兩倍價格

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

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

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

從使用伺服器端轉譯和復原的實際網站收集的成效指標顯示,這很少是最佳選項。最主要的原因是,如果網頁看起來已載入完成,但互動功能都無法運作,會影響使用者體驗。

用戶端轉譯對 TTI 的負面影響。

伺服器端轉譯搭配 Rehydration 則有機會解決這個問題。短期內,如果只對可快取的內容使用伺服器端算繪,可以縮短 TTFB,產生與預先算繪類似的結果。漸進式、逐步或部分重新凍結,可能是未來讓這項技術更可行的關鍵。

串流伺服器端轉譯及逐步重新凍結

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

串流伺服器端轉譯可讓您以區塊形式傳送 HTML,瀏覽器在收到 HTML 時可逐步轉譯。這樣一來,使用者就能更快取得標記,進而加快 FCP。在 React 中,renderToPipeableStream() 中的串流是非同步,而 renderToString() 是同步,這表示背壓處理得很好。

漸進式重新凍結也值得考慮 (React 已實作)。這種做法會隨著時間「啟動」伺服器轉譯應用程式的個別部分,而非目前常見的一次初始化整個應用程式。這有助於減少網頁互動所需的 JavaScript 量,因為您可以延遲用戶端升級網頁中低優先順序的部分,避免阻擋主執行緒,讓使用者互動在使用者啟動後更快發生。

漸進式復水也有助於避免伺服器端轉譯復水最常見的陷阱之一:伺服器轉譯的 DOM 樹狀結構遭到破壞,然後立即重建,這通常是因為初始同步用戶端轉譯所需的資料尚未準備就緒,通常是尚未解析的 Promise。

部分復原

部分復水功能難以實作。這種做法是漸進式復水的延伸,會分析頁面的個別部分 (元件、檢視畫面或樹狀結構),並找出互動性低或沒有反應的部分。對於這些大部分為靜態的部分,系統會將對應的 JavaScript 程式碼轉換為惰性參照和裝飾功能,將用戶端足跡減少到幾乎為零。

部分重新凍結的做法有其問題和缺點。這會對快取帶來一些有趣的挑戰,而用戶端導覽意味著,我們無法假設應用程式閒置部分的伺服器轉譯 HTML 可在不載入完整網頁的情況下使用。

三同構轉譯

如果可以使用 Service Worker,建議您考慮三態轉譯。這項技術可讓您在初始或非 JavaScript 導覽中使用串流伺服器端轉譯,然後讓 Service Worker 在安裝後負責轉譯 HTML。這可讓快取的元件和範本保持最新狀態,並啟用 SPA 樣式的導覽,在同一個工作階段中轉譯新檢視畫面。如果可以在伺服器、用戶端頁面和 Service Worker 之間共用相同的範本和轉送程式碼,這個做法就非常實用。

三態算繪,展示瀏覽器和 Service Worker 與伺服器通訊。

SEO 考量

選擇網頁轉譯策略時,團隊通常會考量對 SEO 的影響。伺服器端轉譯是提供「完整外觀」體驗的熱門選擇,可讓檢索器解讀。檢索器可以解讀 JavaScript,但轉譯方式通常有限制。用戶端轉譯雖然可行,但通常需要額外測試和負擔。最近,如果您的架構高度仰賴用戶端 JavaScript,動態轉譯也成為值得考慮的選項。

結論

決定轉譯方法時,請評估並瞭解瓶頸所在。請考量靜態轉譯或伺服器端轉譯是否能滿足大部分需求。您可以主要傳送 HTML,並搭配少量 JavaScript,提供互動式體驗。這張資訊圖表清楚呈現伺服器與用戶端之間的關係:

算繪選項及其優缺點。

抵免額

感謝所有提供評論和靈感的人:

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