跳至主要內容
版本:0.21

伺服器畫面渲染

預設情況下,Yew 的元件會在 client 端渲染。當使用者瀏覽網站時,伺服器會將不含實際內容的 skeleton HTML 檔和 WebAssembly 程式碼組傳送給瀏覽器。所有內容都是由 WebAssembly 程式碼組在 client 端渲染的。這稱為 client 端渲染。

這種做法適用於大多數網站,但也有一些注意事項

  1. 在整個 WebAssembly 程式碼組下載完畢且初始渲染完成之前,使用者將無法看到任何內容。這可能會導致網路速度較慢的使用者體驗不佳。
  2. 部分搜尋引擎不支援動態渲染的網頁內容,而支援動態渲染的搜尋引擎通常會將動態網站的搜尋結果排名較低。

為了解決這些問題,我們可以在伺服器端渲染我們的網站。

運作原理

Yew 提供了 ServerRenderer,可以在伺服器端渲染網頁。

要在伺服器端呈現 Yew 元件,你可以使用 `ServerRenderer::<App>::new()` 建立一個渲染器並呼叫 `renderer.render().await` 將 `<App />` 渲染成一個 `String`。

use yew::prelude::*;
use yew::ServerRenderer;

#[function_component]
fn App() -> Html {
html! {<div>{"Hello, World!"}</div>}
}

// we use `flavor = "current_thread"` so this snippet can be tested in CI,
// where tests are run in a WASM environment. You likely want to use
// the (default) `multi_thread` favor as:
// #[tokio::main]
#[tokio::main(flavor = "current_thread")]
async fn no_main() {
let renderer = ServerRenderer::<App>::new();

let rendered = renderer.render().await;

// Prints: <div>Hello, World!</div>
println!("{}", rendered);
}

元件生命週期

建議使用函式元件來處理伺服器端渲染。

除了 `use_effect`(以及 `use_effect_with`)以外,所有勾子都可以正常運作,直到一個元件首次成功渲染成 `Html` 為止。

不支援 Web API!

當元件在伺服器端時,是不支援 `web_sys` 等 Web API 的。如果你嘗試使用它們,你的程式將會發生錯誤。你應該將需要使用 Web API 的邏輯隔離在 `use_effect` 或 `use_effect_with` 中,因為這些效果不會在伺服器端渲染中執行。

結構元件

雖然可以在伺服器端渲染中使用結構元件,但是對於客戶端安全的邏輯(例如函式元件的 `use_effect` 勾子)和生命週期事件,並沒有明確的界線,其呼叫順序與客戶端也相異。

此外,結構元件將會持續接受訊息,直到所有子級都渲染完成,並呼叫 `destroy` 方法為止。開發人員必須確保不會將傳遞給元件的訊息連結到使用 Web API 的邏輯。

在設計支援伺服器端渲染的應用程式時,除非有充分的理由,否則請優先使用函式元件。

伺服器端渲染期間的資料擷取

資料擷取是伺服器端渲染和水合中的難點之一。

傳統上,當元件渲染時,它是立即可用的(輸出一個要渲染的虛擬 DOM)。如果元件不想要擷取任何資料,這樣做就可以了。但是如果元件在渲染期間想要擷取一些資料,會怎樣呢?

過去,Yew 沒有機制來偵測元件是否仍在擷取資料。資料擷取客戶端責任為實作一個解法,來偵測在初始渲染期間要求了什麼,並在要求完成後觸發第二次渲染。伺服器重複這個程序,直到在回應傳回前,在渲染期間不會再新增任何待處理要求。

這不僅透過重複渲染元件浪費了 CPU 資源,而且資料客戶端也需要提供一種方法,讓在伺服器端擷取的資料在水化處理期間可用,以確保初始渲染傳回的虛擬 DOM 與伺服器端渲染的 DOM 樹一致,這可能難以實作。

Yew 透過嘗試使用 <Suspense /> 解決這個問題,採取了不同的方法。

Suspense 是在客戶端使用時提供一種方法,可以在元件擷取資料 (已中斷) 時顯示後設 UI,並在資料擷取完成時恢復為正常 UI 的特殊元件。

在伺服器端渲染應用程式時,Yew 會等到元件不再中斷時才將元件序列化到字串緩衝區中。

在水化處理期間,<Suspense /> 元件中的元素會保持未水化狀態,直到其所有子元件都不再中斷。

利用這種方法,開發人員可以建構一個與客戶端無關、支援 SSR、且能輕易擷取資料的應用程式。

SSR 水化

水化是將 Yew 應用程式連接到伺服器端生成的 HTML 檔案的程序。預設情況下,ServerRender 會列印可水化的 HTML 字串,其中包含其他資訊以利水化。呼叫 Renderer::hydrate 方法時,Yew 會調和應用程式產生的虛擬 DOM 與伺服器渲染器產生的 HTML 字串,而不是從頭開始渲染。

注意

若要成功水化 ServerRenderer 建立的 HTML 表示,客戶端必須建立與 SSR 所使用的完全相同的虛擬 DOM 配置,包括不包含任何元素的元件。如果你有任何僅在一個實作中才好用的元件,你可能需要使用 PhantomComponent 來填補額外元件的位置。

警告

只有當實際的 DOM 在瀏覽器初始呈現 SSR 輸出(靜態 HTML)後,符合預期 DOM 時,水化才可能成功。如果您的 HTML 不符合規範,水化 可能會 失敗。瀏覽器可能會變更不正確 HTML 的 DOM 結構,導致實際 DOM 與預期的 DOM 不同。舉例來說,如果您有一個沒有 <tbody> 標籤的 <table>,瀏覽器可能會增加一個 <tbody> 標籤至 DOM

水化期間組元生命週期

在水化期間,組元會在被建立之後,預訂 2 次連續的呈現。在第二次呈現完成後,會呼叫所有效果。確保組元的呈現函式沒有副作用非常重要。它不應該變異任何狀態或觸發其他呈現。如果您的組元目前變異任何狀態或觸發其他呈現,請將它們移至 use_effect 鉤子。

有可能在水化中的伺服器端呈現使用結構體組元,在呼叫已呈現函式之前,會呼叫檢視函式多次。在呼叫已呈現函式之前,DOM 被視為尚未連接,您應該防止有任何存取到已呈現節點,直到呼叫 rendered() 方法。

範例

use yew::prelude::*;
use yew::Renderer;

#[function_component]
fn App() -> Html {
html! {<div>{"Hello, World!"}</div>}
}

fn main() {
let renderer = Renderer::<App>::new();

// hydrates everything under body element, removes trailing
// elements (if any).
renderer.hydrate();
}

範例:simple_ssr 範例:ssr_router

注意

伺服器端呈現目前為實驗性。如果您發現到有一個錯誤,請在 GitHub 上提交一個問題。