- Angular
- TypeScript
- 技術 💻
Angular SSRハイドレーション|incremental hydration実測
Angularのハイドレーションとは、サーバー(SSR)で作ったHTMLを捨てずに再利用し、ブラウザ側でイベントや状態を「つなぎ直す」仕組みです。incremental hydration(インクリメンタルハイドレーション)は、それを @defer ブロック単位で「必要になったときだけ」行う機能です。
この記事では、Angular 22.2.0 で実際にSSRアプリを作成・ビルドし、Playwright(Chromium 141)で 「どのJSファイルが、いつ読み込まれたか」を実測 しました。あわせて、ハイドレーション不一致(NG0500)をわざと起こし、実際に出たコンソールエラー文と、直す前・直した後の結果 を載せています。v22で withIncrementalHydration() が非推奨になり既定で有効になった点など、最新版での書き方の違いもまとめました。
Angular SSR・ハイドレーションの早見表
| やりたいこと | v22の書き方 | 補足 |
|---|---|---|
| SSR付きで新規作成 | ng new my-app --ssr |
provideClientHydration() が最初から入る |
| ハイドレーションを有効化 | provideClientHydration() |
サーバー側設定にも含まれる必要あり(mergeApplicationConfig で共有) |
| incremental hydration を有効化 | 何もしなくてよい(v22から既定で有効) | withIncrementalHydration() は v22.0.0 で非推奨 |
| incremental hydration を無効化 | provideClientHydration(withNoIncrementalHydration()) |
v22で追加された逆方向のスイッチ |
| ハイドレーション前のクリックを再生 | withEventReplay() |
incremental hydration有効時は自動で有効 |
| 見えたらハイドレーション | @defer (hydrate on viewport) { … } |
IntersectionObserverで判定 |
| 操作されたらハイドレーション | @defer (hydrate on interaction) { … } |
click / keydown |
| ホバーでハイドレーション | @defer (hydrate on hover) { … } |
mouseover / focusin |
| 手が空いたらハイドレーション | @defer (hydrate on idle) { … } |
requestIdleCallback相当 |
| 一定時間後 | @defer (hydrate on timer(2s)) { … } |
ms / s 指定 |
| 条件で | @defer (hydrate when isReady()) { … } |
最上位の未ハイドレーションブロックのときだけ評価 |
| 静的なまま(JSを読まない) | @defer (hydrate never) { … } |
配下のネストもすべて対象外 |
| 特定コンポーネントだけ対象外 | <app-x ngSkipHydration /> |
ホスト要素に付ける。ルートに付けると全体が無効 |
| DOM操作をブラウザだけで行う | afterNextRender(() => { … }) |
サーバーでは実行されない |
| API結果の二重取得を防ぐ | provideHttpClient(withFetch()) + 既定の転送キャッシュ |
無効化は withNoHttpTransferCache() |
| i18nブロックもハイドレーション | withI18nSupport() |
既定では i18n ブロックは再描画 |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Angular SSRハイドレーション|incremental hydration実測」
SSR → ハイドレーション → incremental hydration の流れ
3つの段階は「同じHTMLをどこまでブラウザで生かすか」の違い として整理するとわかりやすくなります。
flowchart LR
A[リクエスト] --> B[サーバーでレンダリング<br>SSR]
B --> C[完成済みHTMLを返す<br>すぐに表示される]
C --> D{ブラウザでJSを実行}
D -->|ハイドレーションなし| E[DOMを作り直す<br>ちらつき・CLSの原因]
D -->|フルハイドレーション| F[既存DOMを再利用し<br>全コンポーネントにイベントを接続]
D -->|incremental hydration| G[@defer 以外だけ接続<br>@defer 部分は静的HTMLのまま待機]
G -->|viewport / interaction / idle などの条件| H[そのブロックのJSを読み込み<br>その部分だけハイドレーション]
ポイントは、incremental hydrationでは @defer の中身もサーバー側で描画されてHTMLに入っていること です。通常の @defer(クライアントでの遅延読み込み)ではサーバーは @placeholder を出力しますが、hydrate トリガー付きの場合は本物の中身が出力されます。実際のSSR出力を後で確認します。
検証環境と、SSRプロジェクトの作り方
| 項目 | バージョン |
|---|---|
Angular(@angular/core / CLI / @angular/ssr) |
22.2.0(2026-09-23公開) |
| Node.js | 24.21.0 |
| 計測ブラウザ | Playwright 1.56 + Chromium 141 |
Node.js のバージョンに注意
最初に Node 22.22.2 で ng new を実行したところ、次のメッセージで止まりました。
Node.js version v22.22.2 detected.
The Angular CLI requires a minimum Node.js version of v22.22.3 or v24.15.0 or v26.0.0.
Angular CLI 22 は Node v22.22.3以上 / v24.15.0以上 / v26.0.0以上が必要 です。Node 24 に切り替えると作成できました。
プロジェクト作成(非対話)
npx -y -p @angular/cli@22 ng new hyd-demo --ssr --skip-git --defaults --style=css --skip-tests
cd hyd-demo
生成された src/app/app.config.ts には、すでに provideClientHydration() が入っています。サーバー側の app.config.server.ts は mergeApplicationConfig(appConfig, serverConfig) でこの設定を取り込むため、ブラウザとサーバーの両方に provideClientHydration() が入る 形です(公式ガイドで必須とされている条件です)。
ビルドと起動(allowedHosts に注意)
npx ng build
PORT=7141 node dist/hyd-demo/server/server.mjs
起動後に curl localhost:7141/ すると、最初は次のエラーが返りました。
ERROR: Bad Request ("http://localhost:7141/incremental").
Header "host" with value "localhost:7141" is not allowed.
For more information, see https://angular.dev/best-practices/security#preventing-server-side-request-forgery-ssrf
Angular SSRは、許可したホスト名以外からのリクエストを拒否します(SSRF対策)。angular.json の security.allowedHosts に許可するホストを書いてビルドし直すと表示されました。
"security": {
"allowedHosts": ["localhost"]
}
本番では実際のドメイン名(例:"www.example.com")を入れます。
v22のincremental hydrationの書き方(v19〜21との違い)
v22では provideClientHydration() だけで incremental hydration が有効 です。インストールされた型定義(platform-browser.d.ts)にも「@deprecated Since v22.0.0, incremental hydration is enabled by default with provideClientHydration.」と書かれていました。
| バージョン | 書き方 |
|---|---|
| v19〜v21 | provideClientHydration(withIncrementalHydration())(イベント再生は自動で付く) |
| v22以降 | provideClientHydration() だけでよい。withIncrementalHydration() は非推奨(exportは残っている) |
| v22で無効にしたい | provideClientHydration(withNoIncrementalHydration()) |
withEventReplay() も incremental hydration有効時は自動で有効 になるため、公式ガイドでは「すでに書いてあれば削除してよい」とされています。既存アプリをv22に上げる場合、provideClientHydration(withIncrementalHydration(), withEventReplay()) は provideClientHydration() だけに整理 できます。
@defer (hydrate on …) のコピペ用サンプル
検証で使ったページをそのまま載せます。「いいねボタン」「コメント一覧」「フッター」の3つを、それぞれ違うトリガーで遅らせています。
// src/app/w/widgets.ts(3つの部品。実際の検証では1ファイル1コンポーネントに分けています)
import { Component, signal } from '@angular/core';
@Component({
selector: 'app-like-button',
template: `<button id="like" (click)="count.set(count() + 1)">いいね {{ count() }}</button>`,
})
export class LikeButton { count = signal(0); }
const COMMENTS = Array.from({ length: 300 }, (_, i) => ({ id: i + 1, body: `コメント本文 ${i + 1}:記事の感想や補足がここに入ります。` }));
@Component({
selector: 'app-comment-list',
template: `
<h2>コメント({{ shown().length }}件表示)</h2>
<button id="more" (click)="limit.set(limit() + 5)">もっと見る</button>
<ul>@for (c of shown(); track c.id) { <li>{{ c.body }}</li> }</ul>`,
})
export class CommentList {
limit = signal(5);
shown = () => COMMENTS.slice(0, this.limit());
}
@Component({
selector: 'app-site-footer',
template: `<footer id="footer"><p>運営者情報・利用規約・お問い合わせ(静的な内容)</p></footer>`,
})
export class SiteFooter {}
// src/app/incremental.ts
import { Component } from '@angular/core';
import { LikeButton, CommentList, SiteFooter } from './w/widgets';
@Component({
selector: 'app-incremental',
imports: [LikeButton, CommentList, SiteFooter],
template: `
<h1>記事ページ(incremental hydration)</h1>
@defer (hydrate on interaction) {
<app-like-button />
} @placeholder {
<span>…</span>
}
<div style="height: 2000px">本文…</div>
@defer (hydrate on viewport) {
<app-comment-list />
} @placeholder {
<p>コメントを読み込み中</p>
}
@defer (hydrate never) {
<app-site-footer />
} @placeholder {
<p>フッター</p>
}`,
})
export class IncrementalPage {}
比較用に、同じ3部品を @defer なしで並べた /eager ページも用意しました(部品は別コピーにして、チャンクが混ざらないようにしています)。ルートはどちらも loadComponent で遅延読み込みし、app.routes.server.ts は RenderMode.Server にしています。
// src/app/app.routes.server.ts
import { RenderMode, ServerRoute } from '@angular/ssr';
export const serverRoutes: ServerRoute[] = [
{ path: '**', renderMode: RenderMode.Server },
];
サーバーが返したHTML(実際の出力を抜粋)
curl http://localhost:7141/incremental の結果です(読みやすさのため一部省略)。
<app-like-button jsaction="click:;keydown:;" ngb="d0" ngh="0">
<button id="like" jsaction="click:;" ngb="d0">いいね 0</button>
</app-like-button><!--ngh=d0-->
<div style="height: 2000px;">本文…</div>
<app-comment-list ngh="1"><h2>コメント(5件表示)</h2>
<button id="more" jsaction="click:;" ngb="d1">もっと見る</button>
<ul><li>コメント本文 1:…</li> … </ul>
</app-comment-list><!--ngh=d1-->
<app-site-footer><footer id="footer"><p>運営者情報・利用規約・お問い合わせ(静的な内容)</p></footer></app-site-footer><!--ngh=d2-->
…
<script id="ng-state" type="application/json">{"__nghData__":[…],"__nghDeferData__":{"d0":{"r":1,"s":2},"d1":{"r":1,"s":2,"t":[2]},"d2":{"r":1,"s":2}}}</script>
読み取れることは次のとおりです。
| 出力 | 意味 |
|---|---|
@placeholder ではなく本物の中身が出ている |
hydrate付き @defer はサーバーで中身まで描画 される |
jsaction="click:;" / ngb="d0" |
イベント再生用の目印。どの @defer(d0)に属するかを記録 |
ngh / __nghDeferData__ |
DOMの対応づけ情報と、各 @defer(d0〜d2)の状態 |
先頭の ng-event-dispatch-contract スクリプト |
JS本体より先にクリックを記録しておく小さなスクリプト |
実測:どのJSがいつ読まれたか(hydrateあり/なし)
Playwrightで次の操作を順に行い、.js のレスポンスを時刻つきで記録しました(本番ビルド、localhost のためネットワーク遅延はほぼゼロです)。
- ページを開く(
networkidleまで待つ) - 「いいね」ボタンをクリック
- コメント欄までスクロールし「もっと見る」をクリック
- フッターまでスクロール
実行結果
=== /eager
like before click: いいね 0 / after click: いいね 1 / h2 after more: コメント(10件表示)
139ms 初期表示 chunk-YUeEo57C.js 2038
145ms 初期表示 main-EWSJ7O4U.js 270002
初期表示合計 272040
=== /incremental
like before click: いいね 0 / after click: いいね 1 / h2 after more: コメント(10件表示)
69ms 初期表示 chunk-Cqtk8fQr.js 1210
135ms 初期表示 main-EWSJ7O4U.js 270002
1199ms いいねをクリック chunk-CuBCC2Ee.js 466
2082ms コメント欄までスクロール chunk-Cb5CPOBS.js 967
初期表示合計 271212
表にまとめると次のとおりです(サイズは非圧縮のバイト数)。
| タイミング | /eager(deferなし) |
/incremental |
|---|---|---|
| 初期表示 | main 270,002 + ページ 2,038 | main 270,002 + ページ 1,210 |
| いいねクリック時 | 追加読み込みなし | like-button チャンク 466 |
| コメント欄が見えた時 | 追加読み込みなし | comment-list チャンク 967 |
| フッターが見えた時 | 追加読み込みなし | 読み込まれない(hydrate never) |
| クリック結果 | いいね 0 → 1 | いいね 0 → 1(最初のクリックも失われない) |
わかったこと
@deferの中の部品は、初期表示のJSから外れ、トリガーが起きたときに個別チャンクとして読まれました。hydrate neverのフッターのJSは最後まで読み込まれませんでした。表示はサーバーHTMLのままです。- ハイドレーション前の「いいね」クリックは、チャンク読み込み後に再生されてカウントが1になりました(イベント再生が自動で有効になっている確認)。
- 今回の部品は小さいため差は約0.8KBですが、大きな依存(グラフ描画・リッチエディタなど)を持つ部品ほど効果が大きく なります。main(約270KB)は共通なので、効くのは「ページ固有の重い部品」です。
ハイドレーション不一致(NG0500)をわざと起こしてみる
ハイドレーションは「サーバーのHTML」と「ブラウザでAngularが期待するDOM」が一致している前提で動きます。一致しないと NG0500 系のエラー になります。開発ビルド(ng build --configuration development)で起こした実際のエラーを載せます。
ケース1:<tbody> のない <table>
@Component({
selector: 'app-bad-html',
template: `<table><tr><td>ノートPC</td><td>12</td></tr></table>`,
})
export class BadHtmlPage {}
ブラウザはHTMLを読み込むときに <tbody> を自動で補うため、DOMの形が変わります。実際のコンソール出力です(スタックトレースは省略)。
ERROR RuntimeError: NG0500: During hydration Angular expected <tr> but found <tbody>.
Angular expected this DOM:
<table>
<tr>…</tr> <-- AT THIS LOCATION
…
</table>
Actual DOM is:
<table>
<tbody>…</tbody> <-- AT THIS LOCATION
</table>
Note: attributes are only displayed to better represent the DOM but have no effect on hydration mismatches.
To fix this problem:
* check the "_BadHtmlPage" component for hydration-related issues
* check to see if your template has valid HTML structure
* check if there are any third-party scripts that manipulate the DOM. More info: https://v22.angular.dev/guide/hydration#third-party-scripts-with-dom-manipulation
* or skip hydration by adding the `ngSkipHydration` attribute to its host node in a template
直し方:テンプレートに <tbody> を明示します。
<table><tbody><tr><td>ノートPC</td><td>12</td></tr></tbody></table>
なお、<p> の中に <div> を入れるテンプレートも試しましたが、こちらは ビルド時点でコンパイラが止めてくれました。
✘ [ERROR] NG5002: Unexpected closing tag "p". It may happen when the tag has already been closed by another tag. For more info see https://www.w3.org/TR/html5/syntax.html#closing-elements-that-have-implied-end-tags
<a> の中の <a> も公式ガイドで注意点として挙げられています(今回は未検証)。
ケース2:ネイティブDOM APIで直接書き換える
@Component({
selector: 'app-dom-widget',
template: `<p class="msg">ウィジェット本文</p>`,
})
export class DomWidget {
constructor() {
const host = inject(ElementRef).nativeElement as HTMLElement;
const badge = host.ownerDocument.createElement('b');
badge.textContent = 'NEW';
host.insertBefore(badge, host.firstChild); // テンプレートの外でノードを追加
}
}
結果は次のとおりです。
ERROR RuntimeError: NG0500: During hydration Angular expected <p> but found <b>.
Angular expected this DOM:
<app-dom-widget>
<p>…</p> <-- AT THIS LOCATION
…
</app-dom-widget>
…(以下、ケース1と同じ形式の案内)
さらに最終的なDOMは <b>NEW</b><b>NEW</b><p class="msg">… と バッジが2つ になりました。サーバーで1回、ブラウザで1回追加されたためです。
補足として、最初は host.prepend(badge) と書いていましたが、サーバー側で TypeError: host.prepend is not a function になりました。SSRで使われるDOMはブラウザの完全な実装ではないため、ブラウザ専用の処理はサーバーで動かさない設計が基本になります。
直し方A:afterNextRender でブラウザだけ・描画後に実行
import { Component, ElementRef, afterNextRender, inject } from '@angular/core';
@Component({
selector: 'app-dom-widget-safe',
template: `<p class="msg">ウィジェット本文</p>`,
})
export class DomWidgetSafe {
constructor() {
const host = inject(ElementRef).nativeElement as HTMLElement;
afterNextRender(() => {
const badge = host.ownerDocument.createElement('b');
badge.textContent = 'NEW';
host.insertBefore(badge, host.firstChild);
});
}
}
[log] Angular hydrated 3 component(s) and 10 node(s), 0 component(s) were skipped. 0 defer block(s) were configured to use incremental hydration.
エラーは出ず、DOMは <b>NEW</b><p class="msg">… と バッジ1つ になりました。afterNextRender はサーバーでは実行されず、ブラウザでハイドレーション後に1回だけ実行される ためです。
直し方B:ngSkipHydration でその部品だけ対象外にする
<app-dom-widget ngSkipHydration />
[log] Angular hydrated 2 component(s) and 7 node(s), 1 component(s) were skipped. 0 defer block(s) were configured to use incremental hydration.
「1 component(s) were skipped」と表示され、エラーは出ませんでした。 この部品だけはサーバーのDOMを捨ててブラウザで作り直すため、DOMは <b>NEW</b><p>… の1つになります。ただし再描画のぶんハイドレーションの利点は失われるので、直せない外部ライブラリ(グラフ描画など)向けの一時的な回避策 と考えるのがよいです。
ケース3:Math.random() など、サーバーとブラウザで値が変わる
@Component({
selector: 'app-random',
template: `<p id="rnd">{{ value }}</p>`,
})
export class RandomPage {
value = Math.random().toFixed(4);
}
SSR HTML: 0.2155 / after hydration DOM: 0.5318
この場合はエラーが出ません。ハイドレーションが照合するのはDOMの構造(要素の並び)で、テキストの中身ではないためです。表示はサーバー値からブラウザ値へ静かに切り替わり、ちらつきの原因になります。日時・乱数・ブラウザだけで取れる値は、次の方法でそろえます。
| 値の種類 | そろえ方 |
|---|---|
| APIの結果 | HttpClient(転送キャッシュが既定で有効) |
| 乱数・サーバー時刻など | サーバーで作った値を TransferState に入れて渡す |
window の幅・localStorage など |
afterNextRender の中で読み、描画後に反映する |
不一致エラーの逆引き表
| エラー・症状 | よくある原因 | 直し方 |
|---|---|---|
NG0500 expected <tr> but found <tbody> |
<table> 直下に <tr> |
<tbody> を明示 |
NG0500 expected <p> but found <b> など |
コンストラクタ等で appendChild / insertBefore |
afterNextRender に移す、Renderer2やテンプレートで表現 |
| NG5002 Unexpected closing tag | <p> の中にブロック要素 |
構造を直す(ビルド時に検出) |
… is not a function(サーバーログ) |
ブラウザ専用APIをサーバーで実行 | afterNextRender / isPlatformBrowser で分岐 |
| エラーはないが数値がちらつく | 乱数・日時がサーバーとブラウザで違う | TransferState で値を渡す |
| 外部ライブラリの部品だけ不一致 | ライブラリがDOMを書き換える | その部品に ngSkipHydration |
開発ビルドでは「Angular hydrated N component(s)… N component(s) were skipped」のログが毎回出る ので、スキップ数が想定どおりかを確認する習慣をつけると安心です。
isPlatformBrowser と afterNextRender の使い分け
| 方法 | 実行される場所 | 実行タイミング | 向いている用途 |
|---|---|---|---|
afterNextRender(() => …) |
ブラウザのみ | 次の描画の後(1回) | DOMの計測・外部ライブラリ初期化 |
afterEveryRender(() => …) |
ブラウザのみ | 毎回の描画の後 | スクロール位置の同期など |
isPlatformBrowser(inject(PLATFORM_ID)) |
判定するだけ | 呼んだその場 | サービス内で処理を分けたいとき |
isPlatformBrowser で分岐して描画内容を変えると、それ自体が不一致の原因 になります。localStorage の読み出しなどサービス内の処理は isPlatformBrowser で、描画に関わるものは afterNextRender で「ハイドレーション後に反映」するのが安全です。
HttpClient の転送キャッシュ(二重リクエストを防ぐ)
SSRでAPIを呼ぶと、サーバーで1回、ブラウザで1回と二重に呼ばれそう に見えます。Angularは既定でサーバーの応答をHTMLに埋め込み、ブラウザでは再利用します。
// src/app/app.config.ts(providers に1行追加)
import { provideHttpClient, withFetch } from '@angular/common/http';
providers: [
provideBrowserGlobalErrorListeners(),
provideRouter(routes),
provideHttpClient(withFetch()),
provideClientHydration(),
],
// src/app/data.ts
import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { toSignal } from '@angular/core/rxjs-interop';
interface Item { id: number; name: string; stock: number }
@Component({
selector: 'app-data',
template: `
<h1>在庫一覧</h1>
<ul>
@for (i of items(); track i.id) { <li>{{ i.name }}:{{ i.stock }}</li> }
</ul>`,
})
export class DataPage {
items = toSignal(inject(HttpClient).get<Item[]>('/api/items'), { initialValue: [] });
}
server.ts に /api/items を追加し、呼ばれた回数をログに出して比較しました。
| 設定 | サーバーのAPIログ | ブラウザからの /api/items |
ng-state の中身 |
|---|---|---|---|
既定(provideClientHydration()) |
1回(ua=node) |
0件 | 応答本文(ノートPC・モニター・キーボード)が入る |
provideClientHydration(withNoHttpTransferCache()) |
2回(node とブラウザ) | 1件 | __nghData__ のみ |
既定のままで二重リクエストは起きません。キャッシュ対象を調整したいときは withHttpTransferCacheOptions({ includePostRequests: true }) などで設定します(オプションは公式APIリファレンスで確認してください)。
incremental hydration を入れるときの判断表
| 部品の特徴 | おすすめトリガー | 理由 |
|---|---|---|
| ファーストビューの外にある一覧・コメント | hydrate on viewport |
見えるまでJS不要 |
| 押されるまで何もしないボタン・メニュー | hydrate on interaction |
最初のクリックはイベント再生で失われない |
| ツールチップ・ホバーカード | hydrate on hover |
マウスが乗った時点で準備 |
| すぐ使うが優先度は低い | hydrate on idle |
メイン処理の後に回す |
| 操作しない静的な部品(フッター・規約文) | hydrate never |
JSを一切読まない |
よくあるつまずき
| つまずき | 対応 |
|---|---|
@placeholder を省いた |
hydrate時は使われないが、クライアントで新たに描画するときに必要なので 必ず書く |
| 子だけハイドレーションされない | 親が未ハイドレーションだと子も待つ(親から順に処理される) |
hydrate when が反応しない |
最上位の未ハイドレーションブロックのときだけ評価される |
hydrate never の中が動かない |
配下のネストも含め静的なまま。操作が必要な部品は外に出す |
重い依存を探す(v22.2)
v22.2.0 では npx ng build --stats-json の出力が browser-stats.json と server-stats.json に分かれる ようになりました(実行して確認)。ブラウザ側だけを esbuild の analyze で見れば、@defer に回す候補を探しやすくなります。
よくある質問
Q. v22 で withIncrementalHydration() を書いたままでもよいですか?
A. v22.2.0 の型定義にはまだ含まれていますが、v22.0.0 で非推奨になっています。incremental hydration は既定で有効なので、provideClientHydration() だけに整理しておくと将来の削除に備えられます。無効にしたい場合は withNoIncrementalHydration() を使います。
Q. withEventReplay() は必要ですか?
A. incremental hydration が有効なら自動で有効になります。今回の実測でも、ハイドレーション前のクリックが再生されてカウントが0から1になりました。
Q. ハイドレーションのエラーが本番ビルドで読みにくいのですが?
A. 本番ビルドではメッセージが短縮されます。ng build --configuration development でビルドしてSSRサーバーを起動すると、今回載せたような「期待したDOM/実際のDOM」付きのNG0500が表示されます。
まとめ
- ハイドレーションはSSRのHTMLを再利用し、incremental hydrationは
@defer単位でそれを遅らせる機能 です。 - v22では
provideClientHydration()だけで incremental hydration とイベント再生が有効 になり、withIncrementalHydration()は非推奨です。 - 実測では、
@defer内の部品のJSはクリック時・表示時に初めて読まれ、hydrate neverは最後まで読まれません でした。 - ハイドレーション前のクリックもイベント再生で失われません。
- NG0500は
<tbody>抜けや直接DOM操作で起き、afterNextRenderやngSkipHydrationで解消 できます。 - 乱数や日時はエラーにならずに値が切り替わる ため、
TransferStateやHttpClientの転送キャッシュでそろえます。 - CLI 22 は Node v22.22.3 / v24.15.0 / v26 以上 が必要で、SSRは
allowedHostsの設定 も忘れずに行います。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「Angular SSRハイドレーション|incremental hydration実測」
株式会社RJCでは、SSRやハイドレーションのような新しいフロントエンド技術を、実際に動かして確かめながらプロジェクトの中で仲間と共有しています。こうした知識を共有しながら、日々技術を磨いています。
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。