ウェブ上でのレンダリング

公開日: 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 をクライアントに送信することを回避できます。 これにより、ページの TTBT を削減できます。また、ページ読み込み中にメインスレッドがブロックされる頻度が減るため、 INP の低下にもつながります。メインスレッドが ブロックされる頻度が減ると、ユーザー インタラクションがより早く実行されるようになります。

サーバーサイド レンダリングでは、 テキストとリンクのみをユーザーのブラウザに送信するため、これは理にかなっています。このアプローチは、さまざまな デバイスとネットワークの条件で適切に機能し、興味深いブラウザの最適化 (ドキュメントのストリーミング解析など)を実現します。

サーバーサイド レンダリングと JavaScript の実行が FCP と TTI に影響していることを示す図。
サーバーサイド レンダリングでの FCP と TTI。

サーバーサイド レンダリングを使用すると、ユーザーがサイトを使用できるようになるまで、 CPU バウンドの JavaScript の実行を待つ必要がなくなります。サードパーティの JavaScript を回避できない場合でも、サーバーサイド レンダリングを使用して独自のファーストパーティ JavaScript のコストを削減することで、残りの予算を増やすことができます。ただし、このアプローチには 1 つのトレードオフがあります。 サーバーでページを生成するのに時間がかかるため、ページの TTFB が増加する可能性があります。

サーバーサイド レンダリングがアプリケーションに十分かどうかは、 構築するエクスペリエンスのタイプによって大きく異なります。サーバーサイド レンダリングとクライアントサイド レンダリングの適切なアプリケーションについては長年にわたって議論されていますが、一部のページにサーバーサイド レンダリングを使用し、他のページには使用しないように選択することもできます。一部のサイトでは、ハイブリッド レンダリング手法を導入して成功を収めています。 たとえば、Netflix は比較的静的なランディング ページをサーバーでレンダリングし、 インタラクションの多いページの JavaScript をプリフェッチすることで、 クライアントでレンダリングされるこれらの重いページを 迅速に読み込めるようにしています。

最新のフレームワーク、ライブラリ、アーキテクチャの多くでは、クライアントとサーバーの両方で同じ アプリケーションをレンダリングできます。これらの手法は、 サーバーサイド レンダリングに使用できます。ただし、サーバーとクライアントの両方でレンダリングが行われるアーキテクチャは、パフォーマンス特性とトレードオフが大きく異なる独自のソリューション クラスです。React ユーザーは、サーバーサイド レンダリングに サーバー DOM API または Next.js などの API を基盤とするソリューションNext.jsを使用できます。Vue ユーザーは、Vue の サーバーサイド レンダリング ガイド または Nuxt を使用できます。Angular には Universal があります。

ただし、ほとんどの一般的なソリューションでは何らかの形式のハイドレーションが使用されているため、ツールで使用されている アプローチに注意してください。

静的レンダリング

静的レンダリング はビルド時に行われます。このアプローチでは、ページ上のクライアントサイド JavaScript の量を制限する限り、FCP が高速になり、TBT と INP も低くなります。サーバーサイド レンダリングとは異なり、ページの HTML をサーバーで動的に生成する必要がないため、TTFB も一貫して高速になります。一般的に、静的レンダリングとは、URL ごとに個別の HTML ファイルを 事前に生成することを意味します。事前に生成された HTML レスポンスを使用すると、静的 レンダリングを複数の CDN にデプロイして、エッジ キャッシュを利用できます。

静的レンダリングとオプションの JavaScript 実行が FCP と TTI に影響する様子を示す図。
静的レンダリングでの FCP と TTI。

静的レンダリングのソリューションは、さまざまな形状とサイズがあります。Gatsby などのツールは、ビルドステップとして生成されるのではなく、アプリケーションが動的にレンダリングされているようにデベロッパーに感じさせるように設計されています。11tyJekyllMetalsmithなどの静的サイト生成ツールは、静的な性質を採用し、テンプレート駆動型のアプローチを提供します。

静的レンダリングの欠点の 1 つは、可能なすべての URL に対して個別の HTML ファイルを生成する必要があることです。これらの URL を事前に予測する必要がある場合や、一意のページ数が非常に多いサイトでは、困難な場合や実現不可能な場合があります 。

React ユーザーは、Gatsby、 Next.js の静的エクスポート、または Navi に慣れているかもしれません。これらはすべて、コンポーネントから ページを簡単に作成できます。ただし、静的レンダリングと事前レンダリングの動作は 異なります。静的レンダリングされたページは、クライアントサイド JavaScript をあまり実行しなくても インタラクティブになりますが、事前レンダリングでは、ページを真にインタラクティブにするためにクライアントで起動する必要がある シングルページ アプリケーションの FCP が改善されます。

特定のソリューションが静的レンダリングか事前レンダリングかわからない場合は、 JavaScript を無効にして、テストするページを読み込んでみてください。静的レンダリングされたページでは、JavaScript がなくてもほとんどのインタラクティブ機能が残っています。事前レンダリングされたページには、JavaScript が無効になっているリンクなどの基本的な機能が残っている場合がありますが、ページのほとんどは動作しません。

もう 1 つの便利なテストは、 Chrome DevTools でネットワーク スロットリング を使用して、ページがインタラクティブになるまでにダウンロードされる JavaScript の量を確認することです。 一般的に、事前レンダリングではインタラクティブにするために多くの JavaScript が必要であり、その JavaScript は静的レンダリングで使用される プログレッシブ エンハンスメント アプローチよりも複雑になる傾向があります。

サーバーサイド レンダリングと静的レンダリング

サーバーサイド レンダリングは、その 動的な性質によりコンピューティングのオーバーヘッド コストが大きくなる可能性があるため、すべてのソリューションに最適なわけではありません。多くのサーバーサイド レンダリング ソリューションでは、早期フラッシュ、TTFB の遅延、送信されるデータの倍増 (クライアントの JavaScript で使用されるインライン状態など)が行われません。React では、 renderToString() は同期型でシングル スレッドであるため、遅くなる可能性があります。 新しい React サーバー DOM API はストリーミングをサポートしています。これにより、残りの部分がサーバーで生成されている間に、HTML レスポンスの最初の部分を ブラウザに早く取得できます。

サーバーサイド レンダリングを "適切に" 行うには、コンポーネント キャッシュのソリューションの検索または構築 、メモリ使用量の管理、メモ化手法の使用 などが必要になります。多くの場合、同じアプリを 2 回(クライアントで 1 回、サーバーで 1 回)処理または再構築します。 サーバーサイド レンダリングでコンテンツが早く表示されるからといって、作業量が減るとは限りません。サーバーで生成された HTML レスポンスがクライアントに届いた後にクライアントで多くの作業を行う必要がある場合、ウェブサイトの TBT と INP が高くなる可能性があります。

サーバーサイド レンダリングでは、URL ごとにオンデマンドで HTML が生成されますが、静的レンダリングされたコンテンツを配信するよりも 遅くなる可能性があります。追加の 作業を行うことができれば、サーバーサイド レンダリングとHTML キャッシュ により、サーバーのレンダリング時間を大幅に短縮できます。サーバーサイド レンダリングのメリットは、静的レンダリングよりも多くの「ライブ」データを取得し、より完全なリクエスト セットに応答できることです。パーソナライズが必要なページは、静的レンダリングでは適切に機能しないリクエストの具体的な例です。

PWAページ全体の Service Worker キャッシュを使用する方がよいでしょうか。それとも、個々のコンテンツをサーバーでレンダリングする方がよいでしょうか。

クライアントサイド レンダリング

クライアントサイド レンダリングとは、JavaScript を使用してブラウザでページを直接レンダリングすることを意味します。すべてのロジック、データ取得、テンプレート処理、ルーティングは、サーバーではなく クライアントで処理されます。その結果、サーバーからユーザーのデバイスに送信されるデータが増え、独自のトレードオフが発生します。

クライアントサイド レンダリングは、モバイル デバイスで高速に動作させ、維持するのが難しい場合があります。 JavaScript の予算を厳守し 、できるだけ少ないラウンドトリップ で価値を提供できるように少し工夫することで、クライアントサイド レンダリングで 純粋なサーバーサイド レンダリングのパフォーマンスをほぼ再現できます。パーサーをより迅速に動作させるには、重要なスクリプトとデータを<link rel=preload>配信します。また、PRPLなどのパターンを使用して、最初とそれ以降のナビゲーションが瞬時に行われるようにすることをおすすめします。

クライアントサイド レンダリングが FCP と TTI に影響する様子を示す図。
クライアントサイド レンダリングでの FCP と TTI。

クライアントサイド レンダリングの主な欠点は、アプリケーションの成長に伴って必要な JavaScript の量が増える傾向があり、ページの INP に影響する可能性があることです。 新しい JavaScript ライブラリ、 ポリフィル、サードパーティ コードを追加すると、処理能力が競合し、ページのコンテンツをレンダリングする前に 処理する必要があるため、特に難しくなります。

クライアントサイド レンダリングを使用し、大規模な JavaScript バンドルに依存するエクスペリエンスでは、ページ読み込み時の TBT と INP を削減するために、積極的なコード分割を検討する必要があります。また、JavaScript の遅延読み込みを使用して、ユーザーが必要なときに必要なものだけを提供します。インタラクティビティがほとんどないか まったくないエクスペリエンスの場合、サーバーサイド レンダリングはこれらの問題に対するよりスケーラブルなソリューション となります。

シングルページ アプリケーションを構築している場合は、ほとんどのページで共有されるユーザー インターフェースのコア部分を特定することで、 アプリケーション シェル キャッシュ 手法を適用できます。Service Worker と組み合わせることで、ページがアプリケーション シェルの HTML と依存関係を CacheStorage から非常に迅速に読み込むことができるため、再訪問時の体感パフォーマンスを大幅に向上させることができます。

再ハイドレーションはサーバーサイド レンダリングとクライアントサイド レンダリングを組み合わせたもの

ハイドレーション は、クライアントサイド レンダリングとサーバーサイド レンダリングの両方を行うことで、両者のトレードオフを軽減するアプローチです。ページ全体の読み込みや 再読み込みなどのナビゲーション リクエストは、アプリケーションを HTML にレンダリングするサーバーによって処理されます。レンダリングに使用される JavaScript とデータは、結果のドキュメントに埋め込まれます。 慎重に行うと、サーバーサイド レンダリングと同様に FCP が高速になり、 クライアントで再度レンダリングすることで「ピックアップ」されます。

これは効果的なソリューションですが、パフォーマンスに大きな 欠点が生じる可能性があります。

再ハイドレーションを使用したサーバーサイド レンダリングの主な欠点は、FCP が改善されたとしても、TBT と INP に大きな悪影響を及ぼす可能性があることです。サーバーサイド レンダリングされたページは読み込まれてインタラクティブに見えますが、コンポーネントのクライアントサイド スクリプトが実行され、イベント ハンドラがアタッチされるまで、実際には入力に応答できません。モバイルでは、これに 数分かかることがあり、ユーザーに混乱や不満が生じます。

再ハイドレーションの問題: 2 つのアプリの価格で 1 つのアプリ

クライアントサイド JavaScript が、サーバーが HTML をレンダリングしたすべてのデータを再リクエストすることなく、サーバーが終了したところから正確に引き継ぐには、ほとんどの サーバーサイド レンダリング ソリューションで、UI のデータ 依存関係からのレスポンスがドキュメント内のスクリプトタグとしてシリアル化されます。これにより多くの HTML が重複するため、再ハイドレーションはインタラクティビティの遅延以上の問題を引き起こす可能性があります。

シリアル化された UI、インライン データ、bundle.js スクリプトを含む HTML ドキュメント。

サーバーは、ナビゲーション リクエストに応じてアプリケーションの UI の説明を返しますが、その UI の作成に使用されたソースデータと、クライアントで起動する UI の実装の完全なコピーも返します。UI は、bundle.js の読み込みと実行が完了するまでインタラクティブになりません。

サーバーサイド レンダリングと 再ハイドレーションを使用している実際のウェブサイトから収集されたパフォーマンス指標は、これが最適な選択肢であることはほとんどないことを示しています。最も重要な理由は、ページが準備完了に見えてもインタラクティブ機能が動作しない場合のユーザー エクスペリエンスへの影響です。

クライアントサイド レンダリングが TTI に及ぼす悪影響。

再ハイドレーションを使用したサーバーサイド レンダリングには希望があります。短期的には、キャッシュ可能なコンテンツにのみサーバーサイド レンダリングを使用することで、TTFB を削減し、事前レンダリングと同様の結果を得ることができます。増分 、プログレッシブ、または部分的な再ハイドレーションは、この手法を今後より 実現可能にするための鍵となる可能性があります。

サーバーサイド レンダリングをストリーミングしてプログレッシブに再ハイドレートする

サーバーサイド レンダリングは、ここ数年で多くの発展を遂げてきました。

サーバーサイド レンダリングをストリーミング すると、ブラウザが受信時にプログレッシブにレンダリングできるチャンクで HTML を送信できます。 これにより、マークアップをユーザーに早く提供し、FCP を高速化できます。React では、 が同期型であるのに対し、renderToPipeableStream() が非同期であるため、バックプレッシャーが適切に処理されます。renderToString()

プログレッシブ再ハイドレーション も検討する価値があります (React で実装されています)。この アプローチでは、現在の一般的なアプローチであるアプリケーション全体を一度に初期化するのではなく、サーバーでレンダリングされたアプリケーションの個々の部分が時間とともに「起動」 されます。これにより、ページのインタラクティブ化に必要な JavaScript の量を削減できます。これは、ページの優先度の低い部分のクライアントサイド アップグレードを遅延させてメインスレッドのブロックを防ぎ、ユーザーが開始した後にユーザー インタラクションを早く実行できるようにするためです。

プログレッシブ再ハイドレーションは、サーバーサイド レンダリングの再ハイドレーションの最も一般的な落とし穴の 1 つを回避するのにも役立ちます。サーバーでレンダリングされた DOM ツリーが破棄され、すぐに再構築されます。これは、多くの場合、最初の同期クライアントサイド レンダリングで、まだ準備ができていないデータ(多くの場合、まだ解決されていない Promise)が必要だったためです。

部分的な再ハイドレーション

部分的な再ハイドレーションの実装は難しいことがわかっています。このアプローチは、ページの個々の部分(コンポーネント、ビュー、ツリー)を分析し、インタラクティビティがほとんどない、または反応がない部分を特定するプログレッシブ再ハイドレーションの 拡張です。これらのほとんど静的な部分ごとに、対応する JavaScript コードが不活性な参照と装飾機能に変換され、クライアントサイドのフットプリントがほぼゼロになります。

部分的な再ハイドレーション アプローチには、独自の問題と妥協点があります。キャッシュに興味深い課題が生じます。また、クライアントサイド ナビゲーションでは、ページ全体の読み込みなしで、アプリケーションの不活性な部分のサーバーでレンダリングされた HTML を利用できるとは限りません。

トライソモーフィック レンダリング

Service Worker を使用できる場合は、トライソモーフィック レンダリングを検討してください。この手法 では、最初のナビゲーションまたは JavaScript を使用しないナビゲーションにストリーミング サーバーサイド レンダリングを使用し、インストール後に Service Worker がナビゲーションの HTML のレンダリングを引き継ぐようにします。これにより、キャッシュされたコンポーネントと テンプレートを最新の状態に保ち、同じセッションで新しいビューをレンダリングするための SPA スタイルのナビゲーションを有効にできます。このアプローチは、サーバー、クライアント ページ、Service Worker 間で同じ テンプレート処理コードとルーティング コードを共有できる場合に最適です。

トライソモーフィック レンダリング。ブラウザと Service Worker がサーバーと通信している様子を示しています。

SEO に関する考慮事項

ウェブ レンダリング戦略を選択する際、チームは SEO の影響を考慮することがよくあります。 サーバーサイド レンダリングは、クローラが解釈できる「完全な外観」 のエクスペリエンスを提供するための一般的な選択肢です。クローラは JavaScript を理解できます が、レンダリング方法には 制限 があることがよくあります。クライアントサイド レンダリングは機能しますが、多くの場合、追加の テストとオーバーヘッドが必要です。最近では、アーキテクチャがクライアントサイド JavaScript に大きく依存している場合は、 動的レンダリング も検討する価値のある選択肢となっています。

まとめ

レンダリングのアプローチを決定する際は、ボトルネックを測定して把握します。静的レンダリングまたはサーバーサイド レンダリングで ほとんどの作業を完了できるかどうかを検討します。インタラクティブなエクスペリエンスを実現するために、最小限の JavaScript で HTML を配信しても問題ありません。サーバーとクライアントのスペクトルを示す 便利なインフォグラフィックを次に示します。

レンダリング オプションとそのトレードオフ。

クレジット

レビューとインスピレーションを提供してくださった皆様に感謝いたします。

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