詳細ガイド
サーバーサイド・ハイブリッドレンダリング

ハイドレーション

ハイドレーションとは

ハイドレーションとは、サーバーサイドでレンダリングされたアプリケーションをクライアントサイドで復元するプロセスです。これには、サーバーでレンダリングされたDOM構造の再利用、アプリケーション状態の永続化、サーバーですでに取得されたアプリケーションデータの転送などが含まれます。

なぜハイドレーションが重要なのか

ハイドレーションは、DOMノードの再作成に伴う余計な作業を避けることで、アプリケーションのパフォーマンスを向上させます。Angularは、実行時に既存のDOM要素をアプリケーションの構造に一致させ、可能な限りDOMノードを再利用しようとします。これにより、Core Web Vitals (CWV)統計を使用して測定できるパフォーマンスの向上をもたらします。例えば、First Input Delay (FID)やLargest Contentful Paint (LCP)を削減したり、Cumulative Layout Shift (CLS)を減らしたりできます。これらの数値を改善することで、SEOのパフォーマンスにも影響します。

ハイドレーションを有効にしないと、サーバーサイドでレンダリングされたAngularアプリケーションはアプリケーションのDOMを破棄して再レンダリングするため、UIのちらつきが目に見える可能性があります。この再レンダリングは、LCPなどのCore Web Vitalsに悪影響を与え、レイアウトのずれを引き起こす可能性があります。ハイドレーションを有効にすると、既存のDOMを再利用できるため、ちらつきを防ぐことができます。

Angularでハイドレーションを有効にする方法

ハイドレーションは、サーバーサイドでレンダリングされた(SSR)アプリケーションでのみ有効にできます。サーバーサイドレンダリングを有効にするには、まずAngular SSRガイドを参照してください。

Angular CLIを使用する

Angular CLIを使用してSSRを有効にした場合(アプリケーション作成時に有効にしたか、後でng add @angular/ssrを使用して有効にしたかのどちらか)、ハイドレーションを有効にするコードは、すでにアプリケーションに含まれています。

手動で設定する

カスタム設定を使用していて、Angular CLIを使用してSSRを有効にしていない場合は、メインのアプリケーションコンポーネントまたはモジュールにアクセスし@angular/platform-browserからprovideClientHydrationをインポートすることで、手動でハイドレーションを有効にできます。その後、そのプロバイダーをアプリケーションのブートストラッププロバイダーリストに追加します。

      
import {  bootstrapApplication,  provideClientHydration,} from '@angular/platform-browser';...bootstrapApplication(AppComponent, {  providers: [provideClientHydration()]});

あるいは、NgModuleを使用している場合は、provideClientHydrationをルートアプリケーションモジュールのプロバイダーリストに追加します。

      
import {provideClientHydration} from '@angular/platform-browser';import {NgModule} from '@angular/core';@NgModule({  declarations: [AppComponent],  exports: [AppComponent],  bootstrap: [AppComponent],  providers: [provideClientHydration()],})export class AppModule {}

IMPORTANT: provideClientHydration()呼び出しは、サーバー上でアプリケーションをブートストラップするために使用されるプロバイダーのセットにも含まれていることを確認してください。デフォルトのプロジェクト構造(ng newコマンドで生成された)のアプリケーションでは、ルートAppModuleに呼び出しを追加するだけで十分です。これは、このモジュールがサーバーモジュールによってインポートされているためです。カスタム設定を使用している場合は、サーバーのブートストラップ構成のプロバイダーリストにprovideClientHydration()呼び出しを追加してください。

ハイドレーションが有効になっていることを確認する

ハイドレーションを構成してサーバーを起動したら、ブラウザでアプリケーションをロードしてください。

HELPFUL: ハイドレーションが完全に機能するためには、直接DOM操作のインスタンスを修正する必要がある場合があります。これは、Angularの構造に切り替えるか、ngSkipHydrationを使用することで実現できます。詳細については、制約直接DOM操作、および特定のコンポーネントのハイドレーションをスキップする方法を参照してください。

アプリケーションを開発モードで実行している間は、ブラウザの開発者ツールを開いてコンソールを表示することで、ハイドレーションが有効になっていることを確認できます。ハイドレーション関連の統計を含むメッセージが表示されます。例えば、ハイドレーションされたコンポーネントやノードの数などです。Angularは、サードパーティライブラリから来たコンポーネントを含む、ページにレンダリングされたすべてのコンポーネントに基づいて統計を計算します。

Angular DevToolsブラウザ拡張機能を使用して、ページのコンポーネントのハイドレーションステータスを確認できます。Angular DevToolsでは、ページのどの部分がハイドレーションされたかを示すオーバーレイを有効にできます。ハイドレーションのミスマッチエラーが発生した場合は、DevToolsによってエラーの原因となったコンポーネントも強調表示されます。

イベントのキャプチャと再生

アプリケーションがサーバーでレンダリングされると、生成されたHTMLはロードされるとすぐにブラウザに表示されます。ユーザーはページと対話できると想定するかもしれませんが、イベントリスナーはハイドレーションが完了するまでアタッチされません。v18以降、ハイドレーションの前に発生したすべてのイベントをキャプチャし、ハイドレーションが完了したらそれらのイベントを再生できるイベント再生機能を有効にできます。例えば、withEventReplay()関数を使用して有効にできます。

      
import {provideClientHydration, withEventReplay} from '@angular/platform-browser';bootstrapApplication(App, {  providers: [    provideClientHydration(withEventReplay())  ]});

制約

ハイドレーションは、ハイドレーションが無効な場合に存在しない、アプリケーションにいくつかの制約を課します。アプリケーションは、サーバーとクライアントの両方で同じDOM構造を生成する必要があります。ハイドレーションのプロセスは、DOMツリーが両方の場所で同じ構造を持つことを期待します。これには、Angularがサーバーでのレンダリング中に生成する空白やコメントノードも含まれます。これらの空白とノードは、サーバーサイドレンダリングプロセスによって生成されたHTMLに存在する必要があります。

IMPORTANT: サーバーサイドレンダリング操作によって生成されたHTMLは、サーバーとクライアントの間で変更されてはなりません

サーバーとクライアントのDOMツリー構造にミスマッチがあると、ハイドレーションプロセスは、期待される内容と、DOMに実際に存在する内容を一致させる際に問題が発生します。ネイティブDOM APIを使用して直接DOM操作するコンポーネントが最も一般的な原因です。

直接DOM操作

ネイティブDOM APIを使用してDOMを操作するか、innerHTMLまたはouterHTMLを使用するコンポーネントがある場合、ハイドレーションプロセスでエラーが発生します。DOM操作が問題となる具体的なケースは、documentにアクセスしたり、特定の要素をクエリしたり、appendChildを使用して追加のノードを挿入したりする場合です。DOMノードをデタッチして別の場所に移動しても、エラーが発生します。

これは、AngularがこれらのDOM変更を認識しておらず、ハイドレーションプロセス中に解決できないためです。Angularは特定の構造を期待していますが、ハイドレーションを試みる際に別の構造に出くわします。このミスマッチは、ハイドレーションの失敗につながり、DOMミスマッチエラーが発生します(下記のを参照してください)。

この種のDOM操作を避けるため、コンポーネントをリファクタリングするのが最適です。可能な場合は、Angular APIを使用して作業するようにしてください。この動作をリファクタリングできない場合は、リファクタリング可能なソリューションになるまで、ngSkipHydration属性(下記ので説明)を使用してください。

有効なHTML構造

コンポーネントテンプレートに有効なHTML構造がない場合、ハイドレーション中にDOMミスマッチエラーが発生する可能性があります。

例えば、以下は、この問題の最も一般的なケースです。

  • <tbody>なしの<table>
  • <p>内の<div>
  • 別の<a>内の<a>

HTMLの有効性について不確かであれば、構文バリデーターを使用して確認できます。

Note: HTML標準では、テーブル内に <tbody> 要素を含めることは必須ではありませんが、モダンブラウザは <tbody> 要素を宣言していないテーブルに自動的に <tbody> 要素を作成します。この不一致を避けるために、ハイドレーションエラーを防ぐために常にテーブル内に <tbody> 要素を明示的に宣言してください。

空白の保持構成

ハイドレーション機能を使用する場合は、preserveWhitespacesのデフォルト設定falseを使用することをお勧めします。この設定がtsconfigにない場合、値はfalseとなり、変更は必要ありません。tsconfigpreserveWhitespaces: trueを追加して空白の保持を有効にする場合、ハイドレーションに問題が発生する可能性があります。これはまだ完全にサポートされている構成ではありません。

HELPFUL: この設定が、サーバーのtsconfig.server.jsonとブラウザビルドのtsconfig.app.json一貫して設定されていることを確認してください。値が一致しないと、ハイドレーションが壊れます。

この設定をtsconfigに設定する場合は、デフォルトでtsconfig.server.jsonが継承するtsconfig.app.jsonのみに設定することをお勧めします。

カスタムまたはNoop Zone.jsは、まだサポートされていません

ハイドレーションは、Zone.jsからのシグナルに依存しています。アプリケーション内部が安定すると、Angularはサーバーでシリアライゼーションプロセスを開始するかクライアントでハイドレーション後のクリーンアップを実行して、主張されていないままのDOMノードを削除できます。

カスタムまたは"noop"のZone.js実装を提供すると、"安定"イベントのタイミングが異なり、シリアライゼーションまたはクリーンアップが早すぎるか遅すぎるタイミングでトリガーされる可能性があります。これはまだ完全にサポートされている構成ではなく、カスタムZone.js実装のonStableイベントのタイミングを調整する必要があるかもしれません。

エラー

ノードのミスマッチから、ngSkipHydrationが有効なホストノードで使用された場合まで、様々なハイドレーション関連のエラーが発生する可能性があります。最も一般的なエラーケースは、ネイティブAPIを使用して直接DOM操作したため、ハイドレーションがサーバーでレンダリングされたクライアントの期待されるDOMツリー構造を見つけられないか、一致させることができない場合です。この種のエラーが発生する可能性のあるもう1つのケースは、前の有効なHTML構造セクションで述べたものです。そのため、テンプレートのHTMLが有効な構造を使用していることを確認してください。そうすれば、そのエラーケースを回避できます。

ハイドレーション関連のエラーに関する完全なリファレンスについては、エラーリファレンスガイドを参照してください。

特定のコンポーネントのハイドレーションをスキップする方法

直接DOM操作などの問題により、一部のコンポーネントはハイドレーションを有効にしても正常に動作しない場合があります。回避策として、ngSkipHydration属性をコンポーネントのタグに追加することで、コンポーネント全体をハイドレーションしないようにできます。

      
<app-example ngSkipHydration />

あるいは、ngSkipHydrationをホストバインディングとして設定できます。

      
@Component({  ...  host: {ngSkipHydration: 'true'},})class ExampleComponent {}

ngSkipHydration属性は、Angularにコンポーネント全体とその子孫をハイドレーションしないように指示します。この属性を使用すると、コンポーネントはハイドレーションが無効な場合と同じように動作します。つまり、コンポーネントは破棄され、再レンダリングされます。

HELPFUL: これによりレンダリングの問題は解決されますが、このコンポーネント(とその子孫)については、ハイドレーションの利点は得られません。ハイドレーションを壊すパターン(直接DOM操作など)を避けるため、コンポーネントの実装を調整する必要があります。そうすれば、ハイドレーションをスキップする注釈を削除できます。

ngSkipHydration属性は、コンポーネントのホストノードでのみ使用できます。この属性を他のノードに追加すると、Angularはエラーをスローします。

ルートアプリケーションコンポーネントにngSkipHydration属性を追加すると、事実上、アプリケーション全体でハイドレーションが無効になることに注意してください。この属性を使用する際は注意深く考えてください。これは、最後の手段としての回避策として意図されています。ハイドレーションを壊すコンポーネントは、修正する必要があるバグと見なされるべきです。

ハイドレーションのタイミングとアプリケーションの安定性

アプリケーションの安定性は、ハイドレーションプロセスの重要な部分です。ハイドレーションとハイドレーション後のプロセスは、アプリケーションが安定状態を報告した後にのみ実行されます。安定性が遅れる原因はいくつかあります。例としては、タイムアウトや間隔の設定、未解決のPromise、保留中のマイクロタスクなどがあります。これらの場合、アプリケーションは不安定なままですというエラーが発生する可能性があり、これはアプリケーションが10秒後も安定状態に達していないことを示しています。アプリケーションがすぐにハイドレーションされない場合は、アプリケーションの安定性に影響を与える要因を調べ、リファクタリングして遅延を防いでください。

I18N

HELPFUL: ハイドレーションを使用した国際化のサポートは現在開発者プレビューです。デフォルトでは、Angularはi18nブロックを使用するコンポーネントのハイドレーションをスキップし、事実上、それらのコンポーネントを最初から再レンダリングします。

i18nブロックのハイドレーションを有効にするには、provideClientHydration呼び出しにwithI18nSupportを追加できます。

      
import {  bootstrapApplication,  provideClientHydration,  withI18nSupport,} from '@angular/platform-browser';...bootstrapApplication(AppComponent, {  providers: [provideClientHydration(withI18nSupport())]});

サーバーサイドとクライアントサイドでのレンダリングの一貫性

@ifブロックや、サーバーサイドレンダリング時とクライアントサイドレンダリング時で異なるコンテンツを表示するその他の条件分岐(AngularのisPlatformBrowser関数を使用する@ifブロックなど)は使用しないでください。これらのレンダリングの違いはレイアウトのずれを引き起こし、ユーザー体験とCore Web Vitalsに悪影響を与えます。

DOM操作を行うサードパーティライブラリ

DOM操作に依存してレンダリングを行うサードパーティライブラリがいくつかあります。D3チャートが代表的な例です。これらのライブラリはハイドレーションなしで動作していましたが、ハイドレーションが有効になっているとDOMミスマッチエラーが発生する可能性があります。現時点では、これらのライブラリのいずれかを使用してDOMミスマッチエラーが発生した場合は、そのライブラリを使用してレンダリングを行うコンポーネントにngSkipHydration属性を追加できます。

DOM操作を行うサードパーティスクリプト

多くのサードパーティスクリプト(広告トラッカーや分析ツールなど)は、ハイドレーションが発生する前にDOMを変更します。これらのスクリプトは、ページがAngularが期待する構造と一致しなくなるため、ハイドレーションエラーを引き起こす可能性があります。可能な限り、この種のスクリプトはハイドレーション後まで遅延させることをお勧めします。ハイドレーション後のプロセスが完了するまでスクリプトを遅延させるために、AfterNextRenderの使用を検討してください。

インクリメンタルハイドレーション

インクリメンタルハイドレーションは、ハイドレーションの高度な形態であり、ハイドレーションが発生するタイミングをより細かく制御できます。詳しくはインクリメンタルハイドレーションのガイドを参照してください。