サーバーサイドレンダリング
デフォルトでは、Yewコンポーネントはクライアントサイドでレンダリングされます。閲覧者がウェブサイトにアクセスすると、サーバーは実際のコンテンツのないスケルトンHTMLファイルとWebAssemblyバンドルをブラウザに送信します。すべてはWebAssemblyバンドルによってクライアントサイドでレンダリングされます。これはクライアントサイドレンダリングと呼ばれます。
このアプローチは、いくつかの注意点はあるものの、ほとんどのウェブサイトでうまく機能します。
- WebAssemblyバンドル全体のダウンロードと初期レンダリングが完了するまで、ユーザーは何も見ることができません。これは、低速ネットワークのユーザーにとって悪いエクスペリエンスになる可能性があります。
- 一部の検索エンジンは動的にレンダリングされたWebコンテンツをサポートしておらず、サポートしている検索エンジンでも、動的ウェブサイトは検索結果で下位にランク付けされることがよくあります。
これらの問題を解決するために、ウェブサイトをサーバーサイドでレンダリングすることができます。
仕組み
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_sys`などのWeb APIは、コンポーネントがサーバーサイドでレンダリングされているときは利用できません。使用しようとすると、アプリケーションはパニックを起こします。`use_effect`または`use_effect_with`でWeb APIを必要とするロジックを分離する必要があります。これは、サーバーサイドレンダリング中はエフェクトが実行されないためです。
構造体コンポーネントをサーバーサイドレンダリングで使用することは可能ですが、関数コンポーネントの`use_effect`フックのようなクライアントサイドセーフなロジックとライフサイクルイベントの間に明確な境界がなく、クライアントサイドとは異なる順序で呼び出されます。
さらに、構造体コンポーネントは、すべての子がレンダリングされ、`destroy`メソッドが呼び出されるまで、メッセージを受け取り続けます。開発者は、コンポーネントに渡される可能性のあるメッセージが、Web APIを使用するロジックにリンクしないようにする必要があります。
サーバーサイドレンダリングをサポートするアプリケーションを設計する場合は、正当な理由がない限り、関数コンポーネントを優先してください.
サーバーサイドレンダリング中のデータフェッチ
データフェッチは、サーバーサイドレンダリングとハイドレーションの難しいポイントの1つです。
従来、コンポーネントがレンダリングされると、すぐに利用可能になります(レンダリングされる仮想DOMを出力します)。これは、コンポーネントがデータをフェッチしたくない場合にうまく機能します。しかし、コンポーネントがレンダリング中にデータをフェッチしたい場合はどうなりますか?
以前は、Yewがコンポーネントがまだデータをフェッチしているかどうかを検出するメカニズムがありませんでした。データフェッチクライアントは、初期レンダリング中に何が要求されているかを検出し、要求が満たされた後に2回目のレンダリングをトリガーするソリューションを実装する責任がありました.サーバーはこのプロセスを、レンダリング中に保留中のリクエストが追加されなくなるまで繰り返し、レスポンスを返します。
これは、コンポーネントを繰り返しレンダリングすることで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`を使用して追加コンポーネントの位置を埋めることができます。
ハイドレーションは、ブラウザによるSSR出力(静的HTML)の初期レンダリング後、実際のDOMが期待されるDOMと一致する場合にのみ成功します。HTMLが仕様に準拠していない場合、ハイドレーションは*失敗する*可能性があります。ブラウザは、正しくないHTMLのDOM構造を変更する可能性があり、実際のDOMが期待されるDOMと異なる原因となります。たとえば、`<tbody>`のない`<table>`がある場合、ブラウザはDOMに`<tbody>`を追加する可能性があります
ハイドレーション中のコンポーネントライフサイクル
ハイドレーション中、コンポーネントは作成後に2つの連続したレンダリングをスケジュールします。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に問題を報告してください。