公開日: 2019 年 2 月 6 日、最終更新日: 2026 年 1 月 5 日
ウェブ デベロッパーが下す必要がある重要な決定の 1 つは、アプリケーションのロジックとレンダリングをどこに実装するかです。ウェブサイトの構築方法は多岐にわたるため、この作業は難しい場合があります。
この分野に関する Google の理解は、過去数年間に Chrome で大規模なサイトとやり取りしてきた経験に基づいています。一般的に、デベロッパーには、完全なハイドレーション アプローチよりもサーバーサイド レンダリングまたは静的レンダリングを検討することをおすすめします。
この決定を行う際に選択するアーキテクチャをより深く理解するには、一貫した用語と、各アプローチの共有フレームワークが必要です。これにより、ページ パフォーマンスの観点から、各レンダリング アプローチのトレードオフをより適切に評価できます。
用語
まず、使用する用語を定義します。
レンダリング
- サーバーサイド レンダリング(SSR)
- サーバーでアプリをレンダリングして、JavaScript ではなく HTML をクライアントに送信する。
- クライアントサイド レンダリング(CSR)
- JavaScript を使用して DOM を変更し、ブラウザでアプリをレンダリングする。
- 事前レンダリング
- ビルド時にクライアントサイド アプリケーションを実行して、その初期状態を静的 HTML としてキャプチャします。注: ここでの「プリレンダリング」は、ブラウザによる今後のナビゲーションのプリレンダリングとは異なります。
- 水分補給
- クライアントサイド スクリプトを実行して、サーバー レンダリングされた HTML にアプリケーションの状態とインタラクティビティを追加します。ハイドレーションは、DOM が変更されないことを前提としています。
- リハイドレーション
- ハイドレーションと同じ意味で使用されることが多いですが、リハイドレーションは、最初のハイドレーションの後も含め、最新の状態で DOM を定期的に更新することを意味します。
パフォーマンス
- Time to First Byte(TTFB)
- リンクをクリックしてから、新しいページにコンテンツの最初のバイトが読み込まれるまでの時間。
- First Contentful Paint(FCP)
- リクエストされたコンテンツ(記事本文など)が表示されるまでの時間。
- Interaction to Next Paint(INP)
- ページがユーザー入力に一貫して迅速に応答するかどうかを評価する代表的な指標。
- Total Blocking Time(TBT)
- ページの読み込み中にメインスレッドがブロックされた時間を計算する INP のプロキシ指標。
サーバーサイド レンダリング
サーバーサイド レンダリングでは、ナビゲーションに応じてサーバーでページの完全な HTML を生成します。レンダラはブラウザがレスポンスを取得する前に処理を行うため、クライアントでのデータ取得とテンプレート処理の追加のラウンド トリップを回避できます。
一般的に、サーバーサイド レンダリングでは FCP が速くなります。サーバーでページ ロジックを実行してレンダリングすることで、クライアントに大量の JavaScript を送信することを回避できます。これにより、ページの TBT が減少し、INP の低下にもつながります。これは、ページ読み込み中にメインスレッドがブロックされる頻度が減るためです。メインスレッドのブロック頻度が低いほど、ユーザー インタラクションがより早く実行される可能性が高くなります。
サーバーサイド レンダリングでは、テキストとリンクをユーザーのブラウザに送信するだけなので、これは理にかなっています。このアプローチは、さまざまなデバイスやネットワークの状況で有効に機能し、ストリーミング ドキュメントの解析など、ブラウザの興味深い最適化を可能にします。
サーバーサイド レンダリングを使用すると、ユーザーが CPU バウンドの JavaScript が実行されるまで待つ必要がなくなるため、サイトをすぐに利用できるようになります。サードパーティの JavaScript を避けられない場合でも、サーバーサイド レンダリングを使用してファーストパーティの JavaScript のコストを削減することで、残りの部分に 予算を充てることができます。ただし、このアプローチには 1 つのトレードオフがあります。サーバーでページを生成するのに時間がかかり、ページの TTFB が増加する可能性があります。
サーバーサイド レンダリングがアプリケーションに十分かどうかは、主に構築するエクスペリエンスの種類によって異なります。サーバーサイド レンダリングとクライアントサイド レンダリングの正しい適用方法については長年にわたる議論がありますが、一部のページにはサーバーサイド レンダリングを使用し、他のページには使用しないという選択も可能です。一部のサイトでは、ハイブリッド レンダリング手法が採用され、成功を収めています。たとえば、Netflix は比較的静的なランディング ページをサーバーサイド レンダリングし、インタラクションの多いページの JavaScript をprefetchingすることで、クライアントサイド レンダリングの重いページを高速で読み込む可能性を高めています。
最新のフレームワーク、ライブラリ、アーキテクチャの多くでは、クライアントとサーバーの両方で同じアプリケーションをレンダリングできます。これらの手法は、サーバーサイド レンダリングに使用できます。ただし、サーバーとクライアントの両方でレンダリングが行われるアーキテクチャは、パフォーマンス特性とトレードオフが大きく異なる独自のクラスのソリューションです。React ユーザーは、サーバーサイド レンダリングに サーバー DOM API またはそれに基づいて構築されたソリューション(Next.js など)を使用できます。Vue ユーザーは、Vue のサーバーサイド レンダリング ガイドまたは Nuxt を使用できます。Angular には Universal があります。
ただし、最も一般的なソリューションでは何らかの形式のハイドレーションが使用されているため、ツールで使用されているアプローチに注意してください。
静的レンダリング
静的レンダリングはビルド時に行われます。このアプローチでは、ページ上のクライアントサイド JavaScript の量を制限する限り、FCP を高速化し、TBT と INP を低減できます。サーバーサイド レンダリングとは異なり、ページの HTML をサーバーで動的に生成する必要がないため、TTFB を一貫して高速にすることもできます。一般的に、静的レンダリングとは、各 URL に対して個別の HTML ファイルを事前に生成することを意味します。HTML レスポンスを事前に生成しておくと、静的レンダリングを複数の CDN にデプロイして、エッジ キャッシュを利用できます。
静的レンダリングのソリューションには、さまざまな形態と規模があります。Gatsby などのツールは、ビルドステップとして生成されるのではなく、アプリケーションが動的にレンダリングされているようにデベロッパーが感じられるように設計されています。11ty、Jekyll、Metalsmith などの静的サイト生成ツールは、静的という性質を活かし、テンプレート駆動型のアプローチを提供します。
静的レンダリングの欠点の 1 つは、考えられるすべての URL に対して個別の HTML ファイルを生成する必要があることです。これらの URL を事前に予測する必要がある場合や、ユニーク ページが多数あるサイトの場合、この作業は困難または不可能になる可能性があります。
React ユーザーは、Gatsby、Next.js の静的エクスポート、Navi をご存じかもしれません。これらはすべて、コンポーネントからページを簡単に作成できるものです。ただし、静的レンダリングと事前レンダリングの動作は異なります。静的レンダリングされたページは、クライアントサイドの JavaScript をあまり実行しなくてもインタラクティブになりますが、事前レンダリングは、ページを真にインタラクティブにするためにクライアントで起動する必要があるシングルページ アプリケーションの FCP を改善します。
特定のソリューションが静的レンダリングかプリレンダリングかどうかわからない場合は、JavaScript を無効にして、テストするページを読み込んでみてください。静的にレンダリングされたページでは、JavaScript を使用しなくてもほとんどのインタラクティブ機能が利用できます。プリレンダリングされたページには、JavaScript が無効になっているリンクなどの基本的な機能が残っている場合がありますが、ページのほとんどは機能しません。
もう 1 つの便利なテストは、Chrome DevTools でネットワーク スロットリングを使用して、ページがインタラクティブになるまでにダウンロードされる JavaScript の量を確認することです。一般的に、プリレンダリングをインタラクティブにするにはより多くの JavaScript が必要になり、その JavaScript は静的レンダリングで使用されるプログレッシブ エンハンスメント アプローチよりも複雑になる傾向があります。
サーバーサイド レンダリングと静的レンダリング
サーバーサイド レンダリングは、動的な性質上、コンピューティングのオーバーヘッド コストが大きくなる可能性があるため、すべてのケースに最適なソリューションではありません。多くのサーバーサイド レンダリング ソリューションは、早期フラッシュを行わず、TTFB を遅延させたり、送信されるデータを 2 倍にしたりします(たとえば、クライアントの JavaScript で使用されるインライン状態など)。React では、renderToString() は同期型でシングル スレッドであるため、処理が遅くなる可能性があります。新しい React サーバー DOM API はストリーミングをサポートしています。これにより、HTML レスポンスの最初の部分をブラウザに早く送信し、残りの部分をサーバーで生成し続けることができます。
サーバーサイド レンダリングを「正しく」行うには、コンポーネント キャッシュのソリューションを見つけるか構築する、メモリ消費量を管理する、メモ化手法を使用するなどの対応が必要になります。同じアプリをクライアントとサーバーで 2 回処理または再構築することがよくあります。サーバーサイド レンダリングでコンテンツを早く表示しても、必ずしも作業量が減るとは限りません。サーバーで生成された HTML レスポンスがクライアントに届いた後、クライアント側で多くの作業を行う必要がある場合は、ウェブサイトの TBT と INP が高くなる可能性があります。
サーバーサイド レンダリングでは、各 URL の HTML がオンデマンドで生成されますが、静的にレンダリングされたコンテンツを配信するよりも処理が遅くなる可能性があります。追加の作業が可能であれば、サーバーサイド レンダリングと HTML キャッシュ保存を組み合わせることで、サーバーのレンダリング時間を大幅に短縮できます。サーバーサイド レンダリングのメリットは、静的レンダリングよりも多くの「ライブ」データを取得し、より完全なリクエスト セットに応答できることです。パーソナライズが必要なページは、静的レンダリングではうまく機能しないリクエストの具体的な例です。
サーバーサイド レンダリングは、PWA を構築する際に興味深い判断を必要とすることもあります。フルページのService Worker キャッシュを使用するのと、個々のコンテンツをサーバーレンダリングするのとでは、どちらが優れていますか?
クライアントサイド レンダリング
クライアントサイド レンダリングとは、JavaScript を使用してブラウザでページを直接レンダリングすることです。すべてのロジック、データ取得、テンプレート、ルーティングは、サーバーではなくクライアントで処理されます。結果として、サーバーからユーザーのデバイスに渡されるデータが増えますが、それには独自のトレードオフがあります。
クライアントサイド レンダリングは、モバイル デバイスで高速に動作するように作成して維持するのが難しい場合があります。JavaScript の予算を厳守し、できるだけ少ないラウンド トリップで価値を提供すれば、クライアントサイド レンダリングで純粋なサーバーサイド レンダリングのパフォーマンスをほぼ再現できます。<link rel=preload> を使用して重要なスクリプトとデータを配信することで、パーサーをより迅速に動作させることができます。また、初回とそれ以降のナビゲーションが瞬時に感じられるように、PRPL などのパターンを使用することもおすすめします。
クライアントサイド レンダリングの主な欠点は、アプリケーションの成長に伴って必要な JavaScript の量が増加する傾向があり、ページの INP に影響を与える可能性があることです。新しい JavaScript ライブラリ、ポリフィル、サードパーティ コードが追加されると、処理能力を競い合い、ページのコンテンツをレンダリングする前に処理する必要があることが多いため、特に難しくなります。
クライアントサイド レンダリングを使用し、大きな JavaScript バンドルに依存するエクスペリエンスでは、ページの読み込み時の TBT と INP を下げるために積極的なコード分割を検討し、JavaScript の遅延読み込みを行って、必要なときに必要なものだけを提供する必要があります。インタラクティブ性が低い、またはまったくないエクスペリエンスの場合、サーバーサイド レンダリングはこれらの問題に対するよりスケーラブルなソリューションとなります。
シングルページ アプリケーションを構築している場合、ほとんどのページで共有されるユーザー インターフェースのコア部分を特定することで、アプリケーション シェル キャッシュ保存の手法を適用できます。サービス ワーカーと組み合わせることで、再訪問時の体感パフォーマンスを大幅に改善できます。これは、ページがアプリケーション シェルの HTML と依存関係を CacheStorage から非常に迅速に読み込めるためです。
再ハイドレーションはサーバーサイド レンダリングとクライアントサイド レンダリングを組み合わせたもの
ハイドレーションは、クライアントサイドとサーバーサイドの両方のレンダリングを行うことで、両者のトレードオフを軽減するアプローチです。ナビゲーション リクエスト(ページ全体の読み込みや再読み込みなど)は、アプリケーションを HTML にレンダリングするサーバーによって処理されます。レンダリングに使用される JavaScript とデータは、結果のドキュメントに埋め込まれます。慎重に行うと、サーバーサイド レンダリングのように FCP を高速化し、クライアントで再度レンダリングすることで「ピックアップ」できます。
これは効果的な解決策ですが、パフォーマンスに大きな影響を与える可能性があります。
再ハイドレーションによるサーバーサイド レンダリングの主なデメリットは、FCP が改善されても、TBT と INP に大きな悪影響を及ぼす可能性があることです。サーバーサイドでレンダリングされたページは、読み込まれてインタラクティブになったように見えますが、コンポーネントのクライアントサイド スクリプトが実行され、イベント ハンドラがアタッチされるまで、実際には入力に応答できません。モバイルでは数分かかることがあり、ユーザーの混乱や不満につながる可能性があります。
リハイドレーションの問題: 2 つのアプリの料金で 1 つのアプリ
クライアントサイドの JavaScript が、サーバーが HTML をレンダリングしたすべてのデータを再リクエストすることなく、サーバーが終了したところから正確に引き継ぐために、ほとんどのサーバーサイド レンダリング ソリューションは、UI のデータ依存関係からのレスポンスをドキュメント内のスクリプトタグとしてシリアル化します。これにより HTML が大量に重複するため、再ハイドレーションはインタラクティビティの遅延以上の問題を引き起こす可能性があります。
サーバーはナビゲーション リクエストに応じてアプリケーションの UI の説明を返しますが、その UI の構成に使用されたソースデータと、UI の実装の完全なコピーも返します。このコピーはクライアントで起動されます。bundle.js の読み込みと実行が完了するまで、UI はインタラクティブになりません。
サーバーサイド レンダリングと再ハイドレーションを使用して実際のウェブサイトから収集されたパフォーマンス指標は、これが最良の選択肢となることはほとんどないことを示しています。最も重要な理由は、ページが準備完了に見えるのに、インタラクティブな機能がまったく動作しない場合、ユーザー エクスペリエンスに影響を与えるためです。
再ハイドレーションによるサーバーサイド レンダリングには期待できます。短期的には、キャッシュに保存可能なコンテンツにのみサーバーサイド レンダリングを使用することで TTFB を短縮し、事前レンダリングと同様の結果を得ることができます。段階的、漸進的、または部分的に再ハイドレートすることが、この手法を将来的に実現可能にするための鍵となる可能性があります。
サーバーサイド レンダリングをストリーミングし、段階的に再ハイドレートする
サーバーサイド レンダリングは、ここ数年でさまざまな発展を遂げてきました。
ストリーミング サーバーサイド レンダリングを使用すると、ブラウザが受信時に段階的にレンダリングできる HTML をチャンク単位で送信できます。これにより、マークアップをユーザーにすばやく配信し、FCP を高速化できます。React では、同期の renderToString() と比較して、renderToPipeableStream() でストリームが非同期であるため、バックプレッシャーが適切に処理されます。
プログレッシブ リハイドレーションも検討する価値があります(React で実装されています)。このアプローチでは、サーバー レンダリングされたアプリケーションの個々の部分が、現在の一般的なアプローチであるアプリケーション全体を一度に初期化するのではなく、時間とともに「起動」されます。これにより、ページの優先度の低い部分のクライアントサイドのアップグレードを遅延させてメインスレッドをブロックしないようにし、ユーザーが操作を開始してからユーザー インタラクションがより早く発生するようにできるため、ページをインタラクティブにするために必要な JavaScript の量を減らすことができます。
プログレッシブ再ハイドレーションは、サーバーサイド レンダリングの再ハイドレーションで最もよくある落とし穴の 1 つを回避するのにも役立ちます。サーバーでレンダリングされた DOM ツリーが破棄され、すぐに再構築されるのは、多くの場合、最初の同期クライアントサイド レンダリングで、まだ準備が整っていないデータ(まだ解決されていない Promise など)が必要になったためです。
部分的な再水和
部分的なリハイドレーションの実装は難しいことがわかっています。このアプローチは、ページの個々の部分(コンポーネント、ビュー、ツリー)を分析し、インタラクティビティが低いか、リアクティビティがない部分を特定するプログレッシブ ハイドレーションの拡張機能です。これらのほぼ静的な部分ごとに、対応する JavaScript コードが不活性な参照と装飾機能に変換され、クライアントサイドのフットプリントがほぼゼロに削減されます。
部分的なリハイドレーションのアプローチには、独自の問題と妥協点があります。これはキャッシュ保存にいくつかの興味深い課題をもたらします。また、クライアントサイド ナビゲーションでは、アプリケーションの非アクティブな部分のサーバー レンダリングされた HTML がページ読み込みなしで利用できるとは限りません。
Trisomorphic レンダリング
サービス ワーカーを使用できる場合は、トライソモーフィック レンダリングを検討してください。この手法を使用すると、初回または JavaScript 以外のナビゲーションにストリーミング サーバーサイド レンダリングを使用し、インストール後にナビゲーションの HTML のレンダリングを Service Worker に引き継ぐことができます。これにより、キャッシュに保存されたコンポーネントとテンプレートを最新の状態に保ち、同じセッションで新しいビューをレンダリングするための SPA スタイルのナビゲーションを有効にできます。このアプローチは、サーバー、クライアント ページ、Service Worker 間で同じテンプレートとルーティング コードを共有できる場合に最適です。
SEO に関する考慮事項
ウェブ レンダリング戦略を選択する際、チームは SEO の影響を考慮することがよくあります。サーバーサイド レンダリングは、クローラーが解釈できる「完全な外観」の体験を提供するための一般的な選択肢です。クローラーは JavaScript を理解できますが、レンダリング方法には制限があることがよくあります。クライアントサイド レンダリングは機能しますが、多くの場合、追加のテストとオーバーヘッドが必要になります。最近では、アーキテクチャがクライアントサイドの JavaScript に大きく依存している場合に、ダイナミック レンダリングも検討する価値のある選択肢となっています。
まとめ
レンダリングのアプローチを決定する際は、ボトルネックを測定して把握します。静的レンダリングまたはサーバーサイド レンダリングでほとんどの要件を満たせるかどうかを検討してください。エクスペリエンスをインタラクティブにするために、最小限の JavaScript を含む HTML を主に配信しても構いません。サーバーとクライアントのスペクトルを示す便利なインフォグラフィックを次に示します。
クレジット
レビューとインスピレーションをいただいた皆様に感謝いたします。
Jeffrey Posnick、Houssein Djirdeh、Shubhie Panicker、Chris Harrelson、Sebastian Markbåge。