Wenn die Seite fertig vom Server gerendert ankommt (SSR oder Prerendering), hat der Browser bereits das komplette DOM. Ohne Hydration würde Angular wie in einer CSR-Anwendung starten: Es würde dieses DOM wegwerfen und neu aufbauen — mit sichtbarem Flackern, kaputtem LCP und Layout Shift. Hydration bedeutet, dass Angular das vorhandene DOM durchläuft, mit dem Komponentenbaum abgleicht und wiederverwendet: Es verbindet die Bindings und hängt die Listener an die bereits existierenden Knoten.
Darauf aufbauend gibt es zwei Optimierungen: Event Replay — Klicks vor der Hydration gehen nicht verloren — und inkrementelle Hydration — Teile der Seite bleiben statisches HTML, ohne geladenes JS, bis sie wirklich gebraucht werden.
- Hydration verwendet das vom Server gesendete HTML wieder, statt es neu zu erzeugen, deshalb flackert die Seite beim Start nicht.
- Event Replay merkt sich Klicks, bevor JavaScript geladen ist, und spielt sie danach ab.
- Inkrementelle Hydration (
@defermithydrate on …) lässt Teile der Seite nicht interaktiv, bis sie gebraucht werden. Ihr Code wird erst dann geladen. - Das HTML vom Server und das im Browser müssen identisch sein. Sonst gibt es Mismatch-Fehler mit Codes wie
NG05xx. ngSkipHydrationist der Notausgang für eine Komponente, die das DOM direkt verändert.
provideClientHydration()startet standardmäßig die inkrementelle Hydration und aktiviert automatisch das Event Replay.withIncrementalHydration()ist deprecated (Entfernung in v24 geplant) — nimm es aus der Konfiguration, es ist bereits der Standard.- Die neue Opt-out-Option heißt
withNoIncrementalHydration().
Was die Hydration Schritt für Schritt macht
Auf dem Server annotiert Angular nach dem Rendern das HTML, damit es später „wiedergefunden“ werden kann: das Attribut ngh auf jedem Komponenten-Host (ein Index in die Hydration-Daten), Anker-Kommentare für @if/@for/@defer, ein HTML-Kommentar nghm als Integritätsmarker, jsaction auf den Elementen mit Listenern und ein <script id="ng-state"> mit den Hydration-Daten (__nghData__) und den HTTP-Responses aus dem Transfer Cache.
@Component({
selector: 'app-product-card',
template: `
<h2>{{ product().name }}</h2>
@if (product().stock > 0) {
<button (click)="addToCart()">In den Warenkorb</button>
}
`,
})
export class ProductCard {
readonly product = input.required<Product>();
addToCart() { /* ... */ }
}Im Browser erzeugt Angular beim Bootstrap keine Knoten mehr: Es sucht die vorhandenen anhand der Annotationen. Für jeden Knoten prüft es Typ und Tag (nicht den Text) und hängt die Listener an. Ein abweichender Text ist kein Fehler — er wird bei der ersten Change Detection einfach überschrieben.
Sobald die Anwendung stabil ist (keine laufenden HTTP-Requests und keine PendingTasks), folgt die letzte Phase: Angular entfernt die Server-Knoten, die niemand beansprucht hat, spielt die Events aus der Warteschlange ab und schaltet den Transfer Cache aus. Deshalb hat eine Anwendung, die nie stabil wird, sichtbare Auswirkungen, selbst wenn das DOM korrekt wiederverwendet wurde.
| Ohne Hydration | Volle Hydration | Inkrementelle Hydration | |
|---|---|---|---|
| Das DOM des Servers | weggeworfen und neu erzeugt | wiederverwendet | wiederverwendet |
| JS beim Start nötig | alles | alles | nur, was außerhalb der @defer (hydrate …)-Blöcke liegt |
| Wann ein Block interaktiv wird | nach dem erneuten Rendern | beim Bootstrap, die ganze Seite auf einmal | beim Trigger des Blocks |
| Klicks vor dem JS | verloren | nur mit withEventReplay() wiedergegeben | wiedergegeben; ein Klick auf einen dehydrierten Block hydriert ihn |
provideClientHydration und seine Optionen
Wenn du SSR mit ng add @angular/ssr hinzugefügt hast, steht der Provider bereits in app.config.ts. Ohne Argumente bekommst du in v22 das empfohlene Set:
import { ApplicationConfig } from '@angular/core';
import { provideClientHydration } from '@angular/platform-browser';
export const appConfig: ApplicationConfig = {
providers: [
// v22, ohne Argumente:
// ✓ nicht-destruktive Hydration (nutzt das DOM des Servers weiter)
// ✓ HTTP Transfer Cache (GETs/HEADs vom Server werden im Browser nicht wiederholt)
// ✓ inkrementelle Hydration (@defer mit hydrate …) — inklusive Event Replay
// ✓ im Dev-Modus: provideStabilityDebugging()
provideClientHydration(),
],
};
// app.config.server.ts ruft mergeApplicationConfig(appConfig, serverConfig) auf,
// der Provider landet also auch auf dem Server — Pflicht, sonst erscheint NG0505.| Funktion | Was sie macht | In v22 |
|---|---|---|
provideClientHydration() | nicht-destruktive Hydration + HTTP Transfer Cache + inkrementelle Hydration (mit Event Replay) | das Standard-Set |
withNoIncrementalHydration() | schaltet die inkrementelle Hydration aus (und damit das Event Replay) | neu in v22 |
withEventReplay() | Event Replay ohne inkrementelle Hydration | standardmäßig enthalten; explizit nur mit dem Opt-out oben |
withI18nSupport() | hydriert auch Komponenten mit i18n-Blöcken; ohne sie werden diese komplett neu gerendert | optional |
withHttpTransferCacheOptions({…}) | legt fest, welche Responses im HTML landen: filter, includeHeaders, includePostRequests, includeRequestsWithAuthHeaders… | optional |
withNoHttpTransferCache() | schaltet den Transfer Cache aus: Jeder GET wird zweimal ausgeführt | optional |
withIncrementalHydration() | aktivierte früher die inkrementelle Hydration | deprecated seit v22 (Entfernung in v24 geplant) |
Der Provider muss auch in der Server-Konfiguration vorhanden sein — die vom CLI generierte app.config.server.ts ruft mergeApplicationConfig(appConfig, serverConfig) auf, er ist also automatisch dabei. Fehlt er, annotiert der Server das HTML nicht, und im Browser wird die Seite komplett neu gerendert (im Dev-Modus mit der Warnung NG0505). Widersprüchliche Kombinationen (zum Beispiel withNoHttpTransferCache() mit withHttpTransferCacheOptions()) werfen im Dev-Modus den Fehler NG05001.
Event Replay
Das Problem: Das HTML des Servers ist lange sichtbar, bevor das JS ankommt (in der Timeline auf der SSR-Seite ist das der gelbe Streifen). Der Nutzer sieht einen Button und drückt ihn — aber der Button hat noch keinen Listener.
Der Mechanismus basiert auf JSAction, einer in Angular integrierten Event-Delegation-Bibliothek:
- Markieren — bei der Serialisierung setzt der Server
jsaction="click:;"auf jedes Element, das im Template einen Listener hat. - Abfangen — ein kleines Inline-Skript, direkt nach
<body>eingefügt (der Event Contract), lauscht aufbody-Ebene auf die auf der Seite verwendeten Event-Typen und hält sie im Speicher. - Wiedergeben — nach der Hydration, sobald die Anwendung stabil ist, schickt Angular die gespeicherten Events der Reihe nach an die echten Handler.
Es funktioniert für die nativen Browser-Events (click, mouseover, focusin usw.). Bei der inkrementellen Hydration bleibt der Contract auch nach der Hydration aktiv, sodass er Events in noch dehydrierten Blöcken abfangen kann.
- Drücke „Seite neu laden“ und klicke dann auf die Buttons, solange der JS-Balken nicht voll ist.
Das Replay rettet die Aktion, aber der Button reagiert bis zur Hydration nicht sichtbar. Die CSS-Zustände (:hover, :active, :focus-visible) funktionieren ohne JS — nutze sie, damit der Nutzer weiß, dass der Klick „angekommen“ ist.
Inkrementelle Hydration mit @defer
Ein normales @defer rendert auf dem Server nur den @placeholder: Die Trigger laufen auf dem Server nicht. Ein hydrate-Trigger ändert das: Der Server lädt die Abhängigkeiten des Blocks und rendert den Hauptinhalt. Im Browser bleibt der Block dehydriert — das HTML ist sichtbar, aber der JS-Chunk der darin enthaltenen Komponenten wird nicht einmal geladen —, bis der Trigger feuert. Dann lädt Angular den Chunk, hydriert den Block und spielt die gespeicherten Events ab.
Zwei praktische Folgen. Erstens: Du kannst @defer above the fold verwenden, ohne Layout Shift, weil der endgültige Inhalt bereits auf dem Bildschirm steht und nicht der Placeholder. Zweitens: Ein Event auf einem Element mit Listener in einem dehydrierten Block löst dessen Hydration aus, egal welcher Trigger deklariert ist — der Klick geht nicht verloren. Die Ausnahme ist hydrate never: Sein Teilbaum wird gar nicht annotiert, es gibt also nichts abzuspielen.
<app-header /> <!-- kein @defer: wird beim Bootstrap hydriert -->
@defer (hydrate on interaction) {
<app-gallery [images]="product().images" /> <!-- JS der Galerie: erst beim ersten Klick -->
} @placeholder {
<div class="gallery-skeleton"></div> <!-- nur bei Client-Side-Navigation verwendet -->
}
@defer (hydrate on hover) {
<app-recommendations [productId]="id()" />
} @placeholder {
<div class="reco-skeleton"></div>
}
@defer (hydrate on idle) {
<app-support-chat />
} @placeholder {
<button>Chat</button>
}
@defer (hydrate on viewport) {
<app-reviews [productId]="id()" /> <!-- unterhalb des sichtbaren Bereichs: JS, sobald er im Bild erscheint -->
} @placeholder {
<div class="reviews-skeleton"></div>
}
@defer (hydrate never) {
<app-footer /> <!-- für immer statisches HTML -->
}kein @defer (main.js)dehydriert48 KB, nicht geladenhydrate on interactiondehydriert36 KB, nicht geladenhydrate on hoverdehydriert22 KB, nicht geladenhydrate on idledehydriert30 KB, nicht geladen↓ scrolle runter zu den Bewertungen ↓
hydrate on viewportdehydriert64 KB, nicht geladenhydrate neverstatisch, ohne JS0 KB (wären 18)- Die Simulation startet, sobald die Demo im Bild erscheint (oder drücke „Neu laden“).
Die Hydration-Trigger
@defer (hydrate on idle) { … } <!-- requestIdleCallback -->
@defer (hydrate on idle(500)) { … } <!-- idle, spätestens aber nach 500 ms -->
@defer (hydrate on viewport) { … } <!-- der Inhalt des Blocks erscheint im Bild -->
@defer (hydrate on viewport({ rootMargin: '200px' })) { … } <!-- nur literale Optionen -->
@defer (hydrate on interaction) { … } <!-- click / keydown im Block -->
@defer (hydrate on hover) { … } <!-- mouseover / focusin im Block -->
@defer (hydrate on immediate) { … } <!-- direkt nach dem Rest der Seite -->
@defer (hydrate on timer(2s)) { … } <!-- ms oder s -->
@defer (hydrate when cartReady()) { … } <!-- Bedingung aus der Eltern-Komponente -->
@defer (hydrate never) { … } <!-- nie; keine anderen Hydrate-Trigger -->
@defer (hydrate on viewport; hydrate on timer(5s)) { … } <!-- mehrere = einer davon genügt (OR) -->
<!-- ✗ Kompilierfehler -->
@defer (hydrate on interaction(btn)) { … } <!-- interaction/hover nehmen keine Referenz -->
@defer (hydrate on viewport({ trigger: el })) { … }
@defer (hydrate never; hydrate on idle) { … } <!-- never schließt jeden anderen Hydrate-Trigger aus -->| Trigger | Feuert, wenn | Parameter |
|---|---|---|
hydrate on idle | der Browser frei ist (requestIdleCallback) | optional, ein Timeout: idle(500) |
hydrate on viewport | der Inhalt des Blocks im Bild erscheint | optional, literale IntersectionObserver-Optionen; keine Referenz auf ein anderes Element |
hydrate on interaction | click oder keydown im Block | keine |
hydrate on hover | mouseover oder focusin im Block | keine |
hydrate on immediate | sobald der Rest der Seite fertig gerendert ist | — |
hydrate on timer(…) | nach einem Intervall | erforderlich, in ms oder s |
hydrate when expr | der Ausdruck truthy wird | funktioniert nur beim äußersten dehydrierten Block |
hydrate never | nie: statisches HTML, samt dem ganzen Teilbaum | nicht mit anderen hydrate-Triggern kombinierbar |
Mehrere Trigger am selben Block werden mit „beliebig“ (OR) kombiniert. hydrate when hat eine Einschränkung: Der Ausdruck gehört zur Eltern-Komponente, die selbst hydriert sein muss, damit der Ausdruck ausgewertet werden kann.
Verschachtelte Blöcke
Der Komponentenbaum ist hierarchisch: Ein Kind kann nicht aktiv sein ohne seinen Elternteil. Wenn der Trigger bei einem verschachtelten Block feuert, hydriert Angular die Kette von oben nach unten — zuerst den äußersten dehydrierten Block, dann einen nach dem anderen bis zu dem, der angefragt hat.
@defer (hydrate on interaction) {
<app-product-tabs /> <!-- 1. zuerst wird die Eltern-Komponente hydriert… -->
@defer (hydrate on hover) {
<app-size-guide /> <!-- 2. …dann das Kind, auch wenn der Hover darauf lag -->
}
}Hydrate-Trigger bei CSR und Navigation
Hydration ist eine Optimierung des Erstaufrufs von serverseitig gerendertem Inhalt. Wenn der Nutzer dieselbe Seite über Client-Side-Navigation erreicht (ein routerLink) oder die Anwendung gar kein SSR hat, werden die hydrate-Trigger ignoriert: Der Block verhält sich wie ein normales @defer, mit seinem @placeholder und den normalen Triggern. Hast du keinen normalen Trigger angegeben, fügt der Compiler on idle hinzu.
@defer (on interaction; hydrate on viewport) {
<app-reviews />
} @placeholder {
<button>Bewertungen anzeigen</button>
}
<!--
Erstaufruf, auf dem Server gerenderte Seite:
→ der Server rendert <app-reviews /> (nicht den Placeholder)
→ im Browser bleibt es statisches HTML, bis es den Viewport erreicht (hydrate on viewport)
Client-Side-Navigation (routerLink) oder Anwendung ohne SSR:
→ hydrate on viewport wird ignoriert
→ der @placeholder wird angezeigt, der Block lädt beim Klick (on interaction)
→ ohne normalen Trigger wäre der Standard on idle gewesen
-->@placeholderbleibt nötig — beim Erstaufruf mit SSR wird er nicht angezeigt, bei jeder Client-Side-Navigation zur Seite aber schon.hydrate neverbedeutet „beim Erstaufruf nie JS“. Beim späteren Client-Side-Rendering lädt der Block seine Abhängigkeiten trotzdem, mit dem normalen Trigger.- In einer Anwendung ohne
provideClientHydration()(oder mitwithNoIncrementalHydration()) erzeugen Blöcke mithydrate-Triggern im Dev-Modus die Warnung NG0508.
Mismatch-Fehler
Die Hydration beginnt mit einem Versprechen: Das DOM im Browser ist exakt das, das der Server erzeugt hat, einschließlich Kommentaren und Leerzeichen. Alles, was das Versprechen bricht — der HTML-Parser des Browsers, ein Drittanbieter-Skript, ein CDN, ein anderer Template-Zweig —, endet in einer von drei Situationen: ein Fehler, eine Warnung oder, am heimtückischsten, kein Fehler, aber ein sichtbares erneutes Rendern.
| Code | Bedeutet | Typische Ursache |
|---|---|---|
NG0500 | der gefundene Knoten ist nicht der erwartete (anderer Tag oder Typ) oder fehlt | ungültiges HTML, direkte DOM-Manipulation, ein Drittanbieter-Skript |
NG0501 | Geschwisterknoten fehlen | Knoten von außerhalb von Angular entfernt |
NG0502 | der Knoten wird an der erwarteten Position nicht gefunden | Struktur nach dem Server verändert |
NG0503 | projizierte Knoten außerhalb von Angular erzeugt | createComponent mit projectableNodes aus dem nativen DOM |
NG0504 | ngSkipHydration auf einem Element, das kein Komponenten-Host ist | das Attribut auf einem div |
NG0505 | (Warnung) die Annotationen vom Server fehlen | provideClientHydration() fehlt in der Server-Konfiguration |
NG0506 | (Warnung) die Anwendung wird nicht innerhalb von 10 s stabil | unbeendete PendingTasks; mit Zone.js Timer in der Angular-Zone |
NG0507 | das HTML wurde nach dem Server verändert | ein CDN, das die Kommentare entfernt |
<p class="promo"> <div>Heute kostenlose Lieferung</div> </p>
<p class="promo"><div>Heute kostenlose Lieferung</div></p><p class="promo"></p><div>Heute kostenlose Lieferung</div><p></p>
NG0500: During hydration Angular expected <div> but the node was not found.
Angular expected this DOM:
<p class="promo">
<div>…</div> <-- AT THIS LOCATION
…
</p>
To fix this problem:
* check the "PromoBanner" component for hydration-related issues
* check to see if your template has valid HTML structure
…Was passiert: Der Server baut das DOM über APIs auf, daher übersteht die ungültige Verschachtelung bis zur Serialisierung. Der HTML-Parser des Browsers schließt das <p> jedoch, sobald er das <div> sieht, und Angular findet den erwarteten Knoten nicht mehr.
Lösung: Gültiges HTML: <p> akzeptiert nur Inline-Inhalt. Nimm ein <div class="promo"> als Elternelement oder ein <span> im Inneren.
@if (isPlatformBrowser(…)) in einem Template wirft keinen Fehler, ist aber einer der teuersten Fehler: Angular wirft das DOM des Servers für den Block weg und baut es neu auf, mit Layout Shift. Halte die Ausgabe auf beiden Plattformen identisch und verlege die Browser-only-Initialisierung in afterNextRender — oder nutze @defer, das auf dem Server den Placeholder rendert. Die Varianten findest du auf der Seite Server-kompatibler Code.
ngSkipHydration: der Notausgang
Für eine Komponente, die sich nicht sofort reparieren lässt (typischerweise eine Bibliothek, die direkt ins DOM zeichnet, wie D3), nimmt das Attribut ngSkipHydration auf ihrem Host sie aus der Hydration heraus: Die Komponente und ihre Kinder werden zerstört und neu gerendert, genau wie ohne Hydration. Es funktioniert nur auf Komponenten-Hosts (sonst NG0504), und auf der Root-Komponente deaktiviert es die Hydration für die ganze Anwendung.
<!-- auf dem Host der Komponente, die das DOM direkt manipuliert (z. B. ein D3-Diagramm) -->
<app-sales-chart ngSkipHydration [data]="sales()" />
<!-- ✗ NG0504: nur auf Komponenten-Hosts, nicht auf normalen Elementen -->
<div ngSkipHydration><app-sales-chart [data]="sales()" /></div>Debugging
- Die Konsole im Dev-Modus: Sobald die Anwendung stabil ist, erscheint eine Zusammenfassung. Siehst du sie nicht, hat die Hydration nicht gestartet (prüfe NG0505).
- Angular DevTools zeigt den Hydration-Zustand jeder Komponente und kann ein Overlay über den hydrierten Teilen einblenden; bei einem Mismatch-Fehler hebt es die verantwortliche Komponente hervor.
- Seitenquelltext vs. Elements: Der Seitenquelltext ist das HTML des Servers, Elements ist das aktuelle DOM. Die Unterschiede zwischen beiden (ein zusätzliches
tbody, ein eingeschleustes Banner) sind genau die Ursachen von NG0500. - JavaScript ausschalten in den DevTools und neu laden: Du siehst genau, was der Nutzer vor der Hydration bekommt.
- NG0506:
provideStabilityDebugging()listet die Tasks auf, die die Anwendung instabil halten (mit Zone.js zusätzlichzone.js/plugins/task-trackingimportieren). preserveWhitespacesmuss intsconfig.app.jsonundtsconfig.server.jsondenselben Wert haben; der Standard (false) ist der empfohlene.
Angular hydrated 14 component(s) and 212 node(s), 0 component(s) were skipped.
5 defer block(s) were configured to use incremental hydration.
Learn more at https://angular.dev/guide/hydration.Wann ja, wann nein
- Hydration: immer, wenn du SSR oder Prerendering hast. Ohne sie verliert SSR den halben Sinn (das DOM wird neu aufgebaut).
- Event Replay: standardmäßig aktiv; am wichtigsten auf Seiten mit schnellen Aktionen (Warenkorb, Like, Menü) und bei langsamen Netzen.
hydrate on viewportfür alles unterhalb des sichtbaren Bereichs: Bewertungen, ähnliche Produkte, ein umfangreicher Footer.hydrate on interaction/hoverfür schwere Widgets, die bis zur ersten Berührung brachliegen: Galerien, Filter, Größenauswahl.hydrate neverfür wirklich statischen Inhalt: einen Artikel, AGB, einen Footer ohne Logik.
- Anwendungen ohne SSR: Die
hydrate-Trigger bewirken nichts. Nutze @defer mit normalen Triggern. @defer (hydrate …)bei Komponenten von wenigen KB: Der zusätzliche Chunk und Request kosten mehr, als du sparst.hydrate neverbei etwas mit Listenern oder Zustand: Es funktioniert beim Erstaufruf nie.ngSkipHydrationals dauerhafter „Fix“: Behebe die Ursache (gültiges HTML,afterNextRender, über Angular manipuliertes DOM).isPlatformBrowser-Zweige im Template, um Probleme zu verstecken: Die Alternative ist@deferoderafterNextRender.
Häufige Stolperfallen
nativeElement.appendChild, innerHTML, document.querySelector(…).remove() in Komponenten, die auch auf dem Server laufen, erzeugen Strukturen, die die Hydration nicht abgleichen kann. Nutze das Template (@if, @for, Bindings, Renderer2) und verschiebe reinen Browser-Code in afterNextRender.
- Ungültiges HTML: ein
<div>in einem<p>, ein<a>in einem<a>, eine Tabelle ohne<tbody>. Validiere das Template; der Browser „repariert“ HTML anders, als der Server es geschrieben hat. - Unterschiedliche Werte auf Server und Client (
Date.now(),Math.random(), generierte IDs, Zeitzonen): kein Fehler, aber der Text ändert sich vor den Augen des Nutzers. Übertrage den Wert mitTransferState. - Drittanbieter-Skripte (Analytics, Consent, Chat), die das DOM vor der Hydration verändern: Lade sie danach, mit
afterNextRender. withEventReplay()undwithIncrementalHydration()„sicherheitshalber“ stehen gelassen in v22: Das erste ist überflüssig, das zweite deprecated. Ein schlichtesprovideClientHydration()reicht.- Ein entfernter
@placeholderbei den Blöcken mithydrate, weil „man ihn ja eh nicht sieht“: Bei Client-Side-Navigation sieht man ihn. - Personenbezogene Daten im Transfer Cache: Responses landen im HTML. Requests mit
Authorization/Cookiesind standardmäßig ausgeschlossen — nimm sie nicht ohne Grund mitincludeRequestsWithAuthHeadersauf; nutzefilterodertransferCache: false. - Ein CDN mit HTML-Minifizierung, das die Kommentare entfernt → NG0507 auf jeder Seite.
Spickzettel
// ── Config (app.config.ts; landet über mergeApplicationConfig auch auf dem Server)
provideClientHydration(); // v22: DOM + Transfer Cache + inkrementell + Replay
provideClientHydration(withNoIncrementalHydration(), withEventReplay());
provideClientHydration(withI18nSupport());
provideClientHydration(withHttpTransferCacheOptions({ filter, includeHeaders, includePostRequests }));
provideClientHydration(withNoHttpTransferCache());
http.get(url, { transferCache: false }); // Opt-out pro Request
// ── Template
// @defer (hydrate on idle | idle(ms) | viewport | viewport({ rootMargin }) | interaction | hover | immediate | timer(2s))
// @defer (hydrate when cond) @defer (hydrate never) @defer (on idle; hydrate on viewport)
// <app-chart ngSkipHydration /> host: { ngSkipHydration: 'true' } ← letzter Ausweg
// ── Fehler
// NG0500 anderer Knoten NG0501 Geschwisterknoten fehlen NG0502 Knoten fehlt NG0503 projizierte Knoten außerhalb von Angular erzeugt
// NG0504 ngSkipHydration auf Nicht-Komponente NG0505 Annotationen fehlen (Provider fehlt auf dem Server)
// NG0506 die Anwendung wird nicht innerhalb von 10 s stabil NG0507 HTML nach dem Server verändert (Kommentare entfernt)
// NG0508 Hydrate-Trigger ohne aktive inkrementelle Hydration