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

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

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

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

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

サーバーから提供された HTML をブラウザで表示する仕組み

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

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

この最後のステップが重要です。これはサーバーとブラウザの交換における最も基本的なパフォーマンス最適化のひとつでもあり、ストリーミングと呼ばれています。サーバーが可能な限り早く HTML の送信を開始でき、ブラウザがレスポンス全体の到着を待たなければ、ブラウザは到着時に HTML をチャンク形式で処理できます。

サーバーから送信された HTML の解析のスクリーンショット。Chrome DevTools のパフォーマンス パネルに表示されている。HTML がストリーミングされると、そのチャンクが複数の短いタスクで処理され、レンダリングは増分になります。
Chrome DevTools のパフォーマンス パネルに表示されている、サーバーによって提供された HTML を解析してレンダリングします。HTML の解析とレンダリングに関連するタスクは、チャンクに分割されます。

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

つまり、サーバーから HTML をストリーミングすると、HTML の段階的な解析とレンダリング、メインスレッドへの自動放棄が無料で提供されます。これは、クライアントサイド レンダリングでは得られません。

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

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

SPA 以外でも、JavaScript を介して HTML が動的に DOM に追加される一部のケースでは、クライアント側でレンダリングが行われることがあります。

JavaScript を使用して HTML を作成したり DOM に追加したりするには、次のような方法があります。

  1. innerHTML プロパティを使用すると、既存の要素のコンテンツを文字列で設定できます。ブラウザはこれを DOM に解析します。
  2. document.createElement メソッドを使用すると、ブラウザの HTML 解析を使用せずに、DOM に追加する新しい要素を作成できます。
  3. document.write メソッドを使用すると、ドキュメントに HTML を記述できます(方法 1 と同様に、ブラウザはそれを解析します)。ただし、さまざまな理由により、document.write は使用しないことを強くおすすめします。
JavaScript でレンダリングされた HTML の解析のスクリーンショット。Chrome DevTools のパフォーマンス パネルに表示されている。処理は、メインスレッドをブロックする単一の長いタスク内で行われます。
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 をレンダリングしていることを確認する必要があります。これにより、ウェブサイトで必要になる最初のクライアントサイド レンダリングの量が制限され、エクスペリエンスが向上します。

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

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

ストリーミング Service Worker アーキテクチャを検討する

これは高度な手法であり、あらゆるユースケースで簡単にはうまくいかないかもしれませんが、ユーザーがページ間を移動したときに、瞬時に読み込まれるように MPA をウェブサイトに変えることができます。Service Worker を使用して CacheStorage でウェブサイトの静的部分を事前キャッシュし、ReadableStream API を使用して残りのページの HTML をサーバーから取得することができます。

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

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

おわりに

ウェブサイトがどのように HTML を受信してレンダリングするかは、パフォーマンスに影響します。ウェブサイトを機能させるために必要な HTML のすべて(または大量)の送信をサーバーに依存させると、増分解析とレンダリング、そして時間のかかるタスクを回避するためにメインスレッドに自動的に譲るといった多くのことを無料で得ることができます。

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

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

Maik Jonietz による Unsplash のヒーロー画像。