ハイドレーションとは
ハイドレーションとは、サーバーサイドでレンダリングされたアプリケーションをクライアントで復元するプロセスです。これには、サーバーでレンダリングされた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()]});
あるいは、NgModulesを使用している場合は、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()) ]});
IMPORTANT: イベント再生機能は現在開発者プレビューです。
制約
ハイドレーションは、ハイドレーションが有効になっていない場合に存在しない、アプリケーションにいくつかの制約を課します。アプリケーションは、サーバーとクライアントの両方で同じ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>
<h1>
内の<a>
- 別の
<a>
内の<a>
HTMLの有効性について不確かであれば、構文バリデーターを使用して確認できます。
空白の保持構成
ハイドレーション機能を使用する場合は、preserveWhitespaces
のデフォルト設定false
を使用することをお勧めします。この設定がtsconfig
にない場合、値はfalse
となり、変更は必要ありません。tsconfig
にpreserveWhitespaces: 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
属性を追加すると、事実上、アプリケーション全体でハイドレーションが無効になることに注意してください。この属性を使用する際は注意深く考えてください。これは、最後の手段としての回避策として意図されています。ハイドレーションを壊すコンポーネントは、修正する必要があるバグと見なされるべきです。
I18N
HELPFUL: ハイドレーションを使用した国際化のサポートは現在開発者プレビューです。デフォルトでは、Angularはi18nブロックを使用するコンポーネントのハイドレーションをスキップし、事実上、それらのコンポーネントを最初から再レンダリングします。
i18nブロックのハイドレーションを有効にするには、provideClientHydration
呼び出しにwithI18nSupport
を追加できます。
import { bootstrapApplication, provideClientHydration, withI18nSupport,} from '@angular/platform-browser';...bootstrapApplication(AppComponent, { providers: [provideClientHydration(withI18nSupport())]});
DOM操作を行うサードパーティライブラリ
DOM操作に依存してレンダリングを行うサードパーティライブラリがいくつかあります。D3チャートが代表的な例です。これらのライブラリはハイドレーションなしで動作していましたが、ハイドレーションが有効になっているとDOMミスマッチエラーが発生する可能性があります。現時点では、これらのライブラリのいずれかを使用してDOMミスマッチエラーが発生した場合は、そのライブラリを使用してレンダリングを行うコンポーネントにngSkipHydration
属性を追加できます。