Hydration Angular: ce este, bug-uri tipice și cum le previi
Hydration este pasul prin care Angular preia DOM-ul randat de SSR și îl conectează cu JavaScript, fără re-render. Ce poate merge greșit și cum testezi.
Cuprins
Hydration este pasul prin care un framework JavaScript de tip client (Angular, React, Vue) preia DOM-ul deja randat de server și îl conectează cu starea aplicației și cu event listeners-urile, fără să reconstruiască HTML-ul de la zero. Fără hydration, server-side rendering livrează un HTML corect dar inert: utilizatorul vede conținut, dar butoanele nu reacționează până când JavaScript-ul nu repare conexiunile.
Termenii se confundă frecvent. SSR produce HTML-ul; hydration îl „activează". Dacă SSR-ul eșuează sau lipsește, browserul face re-render complet la pornirea JavaScript-ului, ceea ce anulează avantajul de performanță și SEO al randării pe server. Dacă hydration-ul eșuează parțial, rezultatul este un mismatch vizibil: conținut care clipește sau se resetează brusc după încărcare.
Ce este hydration mai exact?
Un proces de reconciliere. Serverul produce un arbore DOM serializat; clientul primește același arbore și trebuie să construiască un arbore virtual identic pornind de la codul componentelor. Dacă cele două arbori corespund, framework-ul atașează handlere și actualizări reactive pe nodurile existente, fără să atingă DOM-ul. Dacă nu corespund, aruncă o eroare de mismatch și face re-render.
În Angular, hydration nativ este disponibil din versiunea 17 prin provideClientHydration() în app.config.ts. Mecanismul marchează fiecare nod DOM cu un atribut intern de reconciliere; la bootstrap, Angular traversează arborele și verifică că nodurile primite de la server corespund celor așteptate de client.
- Full hydration. Toată pagina este hidratată. Cel mai simplu de implementat, cel mai greu de optimizat la scară.
- Partial hydration. Doar componentele interactive sunt hidratate. Folosit în arhitecturi de tip island (Astro, Qwik). Angular nu oferă această variantă declarativ în versiunea 19.
- Progressive hydration. Componentele sunt hidratate pe măsură ce devin vizibile sau necesare. Util pentru pagini lungi cu multe componente.
De ce este nevoie de hydration în combinație cu SSR?
SSR rezolvă o problemă de indexare și de timp până la primul conținut vizibil (First Contentful Paint). HTML-ul complet ajunge la browser și la Googlebot imediat, fără să aștepte executarea unui bundle JavaScript. Asta contează direct pentru Core Web Vitals: Largest Contentful Paint și Cumulative Layout Shift se îmbunătățesc vizibil față de client-side rendering pur.
Problema este că HTML-ul static nu este interactiv. Un formular de contact, un meniu cu stare, un buton care declanșează o acțiune, toate acestea necesită JavaScript conectat la nodurile DOM corecte. Hydration face acea conexiune. Alternativa, re-randarea completă a clientului, produce un flash vizibil: DOM-ul se golește și se reconstruiește, ceea ce generează layout shift și o experiență degradată.
Care sunt bug-urile tipice de hydration?
- DOM mismatch pe timestamp sau date dinamice. Dacă o componentă afișează
Date.now()sau ora curentă, serverul randează o valoare, clientul calculează alta câteva milisecunde mai târziu. Angular detectează diferența și face re-render pe nodul respectiv. Soluție: preia data din datele primite de la server, nu o recalcula în client. - Conținut condiționat de
windowsaunavigator. Pe server,windownu există. Orice bloc de cod gated detypeof window !== 'undefined'sau deisPlatformBrowser()produce un arbore diferit pe server față de client. Angular va arunca o eroare de mismatch și va reconstrui componenta. Modelul corect este să randezi un placeholder pe server și să înlocuiești conținutul dependent de browser după hydration. - Double render și layout shift. Când hydration eșuează pe o componentă, Angular face re-render complet pe ea. Dacă componenta este vizibilă deasupra foldului, utilizatorul vede un flash. Acesta se traduce direct în CLS (Cumulative Layout Shift), care afectează scorul Core Web Vitals.
- Flickering la date încărcate asincron. Dacă componenta face un fetch separat în browser după hydration, cu date diferite față de cele randate pe server (de exemplu, cache SSR expirat), conținutul clipește la schimbare. Soluție: asigură-te că SSR-ul și clientul folosesc aceeași sursă de date, de preferință transferată via
TransferStatedin Angular. - Subscription leak în procesul SSR. Un
Observabledeschis fărătakeUntilDestroyedsauasyncpipe rămâne activ după ce componenta și-a terminat randarea. Pe un browser, componenta se distruge și subscription-ul moare. Pe server, procesul SSR rulează continuu; subscription-urile se acumulează cerere după cerere și consumă memorie.
Cum testezi corectitudinea hydration-ului?
Cel mai direct test: deschide DevTools, fila Console, și caută mesaje de tipul NG0500 sau NG0501 (Angular hydration mismatch errors). Dacă nu apar, hydration-ul a trecut fără probleme. Dacă apar, mesajul include calea componentei și tipul de nod unde s-a produs discrepanța.
O verificare mai sistematică combină trei unghiuri. Primul, compară HTML-ul răspunsului SSR (din curl) cu HTML-ul din DOM după ce JavaScript s-a executat; dacă diferă în noduri semnificative, ai un mismatch. Al doilea, rulează pagina prin Lighthouse și urmărește Cumulative Layout Shift: un scor CLS ridicat indică adesea că hydration face re-render pe componente vizibile. Al treilea, monitorizează memoria procesului SSR pe un interval de câteva ore cu un instrument de observabilitate; o creștere liniară constantă indică un subscription leak.
Pentru consistență pe mai multe randări, un MutationObserver aplicat pe document.body poate înregistra orice mutație de DOM care apare după DOMContentLoaded. Dacă se înregistrează mutații pe noduri importante (titlul articolului, prețul unui produs), ești într-un caz de double render.
Cum gestionăm hydration pe paginile publice crawlerra.com?
Pentru paginile publice ale crawlerra.com folosim Angular 19 cu SSR și hydration nativ (provideClientHydration()). Procesul SSR rulează cu --max-old-space-size=192 MB ca heap cap1. Limita ne forțează să prindem rapid orice subscription leak sau accumulator care ar ține obiecte SSR vii dincolo de cererea curentă: procesul cade, systemd îl repornește curat, iar noi vedem evenimentul în log-uri și investigăm. Un proces care crește lent până la OOM pe un VPS de 1.8 GB este mult mai costisitor de diagnosticat decât unul care cade repede și produce un restart vizibil în metrici.
O pagină tipică de articol generează un HTML de aproximativ 47 KB2, complet randat, cu JSON-LD, meta tag-uri și conținut vizibil. Hydration-ul preia acel arbore și îl activează fără re-render, ceea ce înseamnă că utilizatorul nu vede flash și Googlebot primește conținutul complet la prima cerere. Ghidul extins despre setup-ul SSR în producție, inclusiv cum evităm referințele la window în componente, este în ghidul nostru despre Angular SSR pentru produse reale.
- Limita de heap
--max-old-space-size=192a fost setată după ce procesul SSR a depășit 800 MB RSS în 48 de ore din cauza unui abonament reactiv care nu se închidea.[ops.angular_memory] - Mărimea HTML-ului SSR pentru un articol din blog, măsurată cu
curl -sS https://crawlerra.com/blog/cum-scriem-articolele | wc -c.[crawlerra.ssr_body_size]
Întrebări frecvente
Ce se întâmplă dacă Angular nu poate face hydration pe un nod DOM?
Angular aruncă o eroare de mismatch și face un re-render complet al componentei afectate. Rezultatul este un flash vizibil (conținutul dispare și reapare) și o pierdere a avantajului SSR pentru zona respectivă. Cauza cea mai frecventă este conținut condiționat de window sau navigator, absent pe server.
Hydration Angular este activat implicit sau trebuie configurat?
Din Angular 17 hydration nativ este disponibil, dar trebuie activat explicit cu provideClientHydration() în app.config.ts. Înainte de versiunea 17, singura variantă era re-randarea completă a clientului, ceea ce producea un flash de conținut și un CLS vizibil pe paginile cu SSR.
Ce diferență este între hydration și re-render la CSR?
La hydration, Angular reutilizează DOM-ul existent primit de la server; la CSR clasic, Angular șterge totul și reconstruiește de la zero. Hydration este mai rapid și elimină flash-ul de conținut; dezavantajul este că orice nepotrivire între HTML-ul serverului și cel al clientului produce erori de mismatch care pot fi greu de depanat.
Partial hydration există în Angular?
Nu oficial, la nivelul Angular 19. Arhitecturi ca island architecture (Astro, Qwik) sau partial hydration (Next.js cu React Server Components) permit să hidratezi selectiv componentele interactive. Angular îl îmbunătățește pe fronturi similare (deferrable views, signal-based rendering), dar nu oferă partial hydration declarativ în versiunea curentă.
Cum detectez un memory leak legat de hydration în procesul SSR?
Monitorizează RSS-ul procesului Node care rulează SSR pe un interval de câteva ore. O creștere liniară constant indică un abonament reactiv care nu se închide după randarea cererii. Cea mai directă cauză: un Observable deschis în constructorul unei componente fără takeUntilDestroyed sau async pipe, activ pe tot ciclul de viață al procesului, nu al componentei.