HTML のクライアントサイド レンダリングとインタラクティビティ

JavaScript を使用した HTML のレンダリングは、サーバーから送信された HTML のレンダリングとは異なり、パフォーマンスに影響する可能性があります。このガイドでは、その違いと、ウェブサイトのレンダリング パフォーマンスを維持するためにできること(特にインタラクションに関するもの)について説明します。

HTML の解析とレンダリングは、ブラウザの組み込みナビゲーション ロジック(「従来のページ読み込み」や「ハード ナビゲーション」と呼ばれることもあります)を使用するウェブサイトでは、ブラウザがデフォルトで非常にうまく処理します。このようなウェブサイトは、マルチページ アプリケーション(MPA)と呼ばれることもあります。

ただし、デベロッパーはアプリケーションのニーズに合わせてブラウザのデフォルトを回避できます。これは、シングルページ アプリケーション(SPA)パターンを使用するウェブサイトに当てはまります。このパターンでは、JavaScript を使用してクライアント側で HTML/DOM の大部分を動的に作成します。この設計パターンはクライアントサイド レンダリングと呼ばれ、関連する作業が過剰な場合、ウェブサイトの Interaction to Next Paint(INP)に影響を与える可能性があります。

このガイドでは、サーバーからブラウザに送信された HTML を使用する場合と、JavaScript を使用してクライアントで HTML を作成する場合の違いを比較し、後者の場合、重要なタイミングでインタラクションのレイテンシが高くなる可能性がある理由について説明します。

ブラウザがサーバーから提供された HTML をレンダリングする方法

従来のページ読み込みで使用されるナビゲーション パターンでは、ナビゲーションのたびにサーバーから HTML を受け取ります。ブラウザのアドレスバーに URL を入力するか、MPA のリンクをクリックすると、次のイベントが順に発生します。

  1. ブラウザが、指定された URL のナビゲーション リクエストを送信します。
  2. サーバーは HTML をチャンクで応答します。

このうち、最後のステップが重要です。また、サーバーとブラウザ間のやり取りにおける最も基本的なパフォーマンス最適化の 1 つでもあり、ストリーミングと呼ばれています。サーバーが HTML をできるだけ早く送信し始め、ブラウザがレスポンス全体が届くのを待たない場合、ブラウザは HTML をチャンク単位で処理できます。

Chrome DevTools のパフォーマンス パネルで視覚化された、サーバーから送信された HTML の解析のスクリーンショット。HTML がストリーミングされると、そのチャンクが複数の短いタスクで処理され、レンダリングが段階的に行われます。
サーバーから提供された HTML の解析とレンダリング。Chrome DevTools の [パフォーマンス] パネルで可視化されます。HTML の解析とレンダリングに関連するタスクはチャンクに分割されます。

ブラウザで発生するほとんどの処理と同様に、HTML の解析はタスク内で行われます。HTML がサーバーからブラウザにストリーミングされると、ブラウザはストリームのビットがチャンク単位で到着するたびに少しずつ HTML の解析を最適化します。その結果、ブラウザは各チャンクの処理後にメインスレッドに定期的に処理を譲るため、長いタスクを回避できます。つまり、HTML の解析中に、ユーザーにページを表示するために必要な増分レンダリング作業や、ページの重要な起動期間中に発生する可能性のあるユーザー操作の処理など、他の作業を行うことができます。このアプローチにより、ページの Interaction to Next Paint(INP)スコアが向上します。

つまり、サーバーから HTML をストリーミングすると、HTML の増分解析とレンダリングが無料で実行され、メインスレッドへの自動イールドも行われます。クライアントサイド レンダリングでは、このようなことは起こりません。

ブラウザが JavaScript によって提供された HTML をレンダリングする方法

ページへのナビゲーション リクエストごとに、サーバーから HTML を提供する必要がありますが、一部のウェブサイトでは SPA パターンが使用されます。このアプローチでは、サーバーから最小限の HTML の初期ペイロードが提供されますが、クライアントはサーバーから取得したデータから組み立てられた HTML を使用して、ページのメイン コンテンツ領域に入力します。その後のナビゲーション(この場合は「ソフト ナビゲーション」と呼ばれることもあります)は、JavaScript によって完全に処理され、新しい HTML でページが入力されます。

クライアントサイド レンダリングは、JavaScript を介して HTML が DOM に動的に追加される、より限定的なケースで SPA 以外のサイトでも発生することがあります。

JavaScript を使用して HTML を作成したり DOM に追加したりする方法はいくつかあります。

  1. innerHTML プロパティを使用すると、文字列を介して既存の要素にコンテンツを設定できます。ブラウザは、この文字列を DOM に解析します。
  2. document.createElement メソッドを使用すると、ブラウザの HTML 解析を使用せずに、DOM に追加する新しい要素を作成できます。
  3. document.write メソッドを使用すると、ドキュメントに HTML を書き込むことができます(ブラウザはアプローチ 1 と同様に HTML を解析します)。ただし、いくつかの理由から、document.write の使用は強く推奨されません。
Chrome DevTools の [パフォーマンス] パネルで可視化された、JavaScript でレンダリングされた HTML の解析のスクリーンショット。作業は、メインスレッドをブロックする単一の長時間タスクで行われます。
Chrome DevTools のパフォーマンス パネルに表示される、クライアントでの JavaScript による HTML の解析とレンダリング。解析とレンダリングに関わるタスクがチャンク化されず、メインスレッドをブロックする長時間タスクが発生します。

クライアントサイドの JavaScript を使用して HTML/DOM を作成すると、次のような重大な影響が生じる可能性があります。

  • ナビゲーション リクエストに応じてサーバーからストリーミングされる HTML とは異なり、クライアント側の JavaScript タスクは自動的にチャンク化されません。そのため、メインスレッドをブロックする長いタスクが発生する可能性があります。つまり、クライアントで一度に大量の HTML/DOM を作成すると、ページの INP に悪影響が及ぶ可能性があります。
  • 起動時にクライアントで HTML が作成された場合、その HTML 内で参照されているリソースはブラウザのプリロード スキャナによって検出されません。これは、ページの Largest Contentful Paint(LCP)に悪影響を及ぼす可能性があります。これは実行時のパフォーマンスの問題ではなく(重要なリソースの取得におけるネットワーク遅延の問題)、この基本的なブラウザ パフォーマンスの最適化を回避することでウェブサイトの LCP に影響が出ないようにする必要があります。

クライアントサイド レンダリングのパフォーマンスへの影響に対する対策

ウェブサイトがクライアントサイド レンダリングに大きく依存しており、フィールド データで INP の値が低い場合、クライアントサイド レンダリングが問題に関係しているかどうか疑問に思うかもしれません。たとえば、ウェブサイトが SPA の場合、フィールド データから、レンダリング処理に大きな影響を与えるインタラクションを特定できます。

原因が何であれ、問題を解決するために確認できる原因をいくつかご紹介します。

サーバーからできるだけ多くの HTML を提供する

前述のように、ブラウザはデフォルトでサーバーからの HTML を非常に効率的に処理します。これにより、長いタスクを回避し、メインスレッドの合計時間を最適化するように HTML の解析とレンダリングが分割されます。これにより、Total Blocking Time(TBT)が短縮され、TBT は INP と強い相関関係があります。

ウェブサイトの構築にフロントエンド フレームワークを使用している場合があります。その場合は、サーバーでコンポーネントの HTML をレンダリングする必要があります。これにより、ウェブサイトで必要な初期クライアントサイド レンダリングの量が制限され、エクスペリエンスの向上につながります。

  • React の場合は、Server DOM API を使用してサーバーで HTML をレンダリングします。ただし、従来のサーバーサイド レンダリング方式では同期アプローチが使用されるため、最初のバイトまでの時間(TTFB)が長くなる可能性があります。また、First Contentful Paint(FCP)や LCP などの後続の指標にも影響する可能性があります。可能な場合は、Node.js またはその他の JavaScript ランタイムのストリーミング API を使用して、サーバーが HTML のブラウザへのストリーミングをできるだけ早く開始できるようにします。React ベースのフレームワークである Next.js には、デフォルトで多くのベスト プラクティスが用意されています。サーバーで HTML を自動的にレンダリングするだけでなく、ユーザー コンテキスト(認証など)に基づいて変化しないページの HTML を静的に生成することもできます。
  • Vue は、デフォルトでクライアントサイド レンダリングも実行します。ただし、React と同様に、Vue でもサーバーでコンポーネントの HTML をレンダリングできます。可能な場合はこれらのサーバーサイド API を活用するか、Vue プロジェクトで上位レベルの抽象化を検討して、ベスト プラクティスを実装しやすくします。
  • Svelte はデフォルトで サーバーで HTML をレンダリングします。ただし、コンポーネント コードでブラウザ専用のネームスペース(window など)にアクセスする必要がある場合は、そのコンポーネントの HTML をサーバーでレンダリングできないことがあります。不要なクライアントサイド レンダリングが発生しないように、可能な限り代替アプローチを検討します。SvelteKit(Svelte に対する Next.js のようなもの)は、可能な限り多くのベスト プラクティスを Svelte プロジェクトに組み込むため、Svelte のみを使用するプロジェクトで潜在的な落とし穴を回避できます。

クライアントで作成される DOM ノードの量を制限する

DOM が大きいと、レンダリングに必要な処理が増える傾向があります。ウェブサイトが本格的な SPA であっても、MPA のインタラクションの結果として既存の DOM に新しいノードを挿入する場合でも、DOM をできるだけ小さく保つことを検討してください。これにより、クライアントサイド レンダリングで HTML を表示する際に必要な作業を減らすことができ、ウェブサイトの INP を低く抑えることができます。

ストリーミング サービス ワーカー アーキテクチャを検討する

これは高度な手法であり、すべてのユースケースで簡単に機能するとは限りませんが、ユーザーがページ間を移動する際に瞬時に読み込まれるように感じられるウェブサイトに MPA を変えることができます。サービス ワーカーを使用して、ウェブサイトの静的部分を CacheStorage にプリキャッシュし、ReadableStream API を使用して、ページの残りの HTML をサーバーから取得できます。

この手法をうまく使用すると、クライアントで HTML を作成することはありませんが、キャッシュからコンテンツ パーシャルを瞬時に読み込むことで、サイトの読み込みが速いという印象を与えることができます。このアプローチを使用するウェブサイトは、クライアントサイド レンダリングの欠点がない SPA のように感じられます。また、サーバーからリクエストする HTML の量を減らすこともできます。

つまり、ストリーミング サービス ワーカー アーキテクチャは、ブラウザの組み込みナビゲーション ロジックを置き換えるのではなく、それに追加するものです。Workbox でこれを実現する方法については、ストリームによる高速なマルチページ アプリケーションをご覧ください。

まとめ

ウェブサイトが HTML を受信してレンダリングする方法は、パフォーマンスに影響します。ウェブサイトの機能に必要な HTML のすべて(または大部分)をサーバーから送信する場合、増分解析とレンダリング、長いタスクを回避するためのメインスレッドへの自動イールドなど、多くのメリットがあります。

クライアントサイドの HTML レンダリングでは、多くの場合回避可能なパフォーマンスの問題がいくつか発生する可能性があります。ただし、個々のウェブサイトの要件により、常に 100% 避けることはできません。クライアントサイド レンダリングの過剰な使用によって発生する可能性のある長いタスクを軽減するには、可能な限りウェブサイトの HTML をサーバーから送信し、クライアントでレンダリングする必要がある HTML の DOM サイズをできるだけ小さくします。また、サーバーから読み込まれた HTML に対してブラウザが提供する増分解析とレンダリングを利用しながら、クライアントへの HTML の配信を高速化する代替アーキテクチャを検討します。

ウェブサイトのクライアントサイド レンダリングを可能な限り最小限に抑えることができれば、ウェブサイトの INP だけでなく、LCP や TBT などの他の指標も改善され、場合によっては TTFB も改善される可能性があります。

ヒーロー画像: UnsplashMaik Jonietz 氏によるもの。