Saltar al contenido
Angular•6 min de lectura

Angular 22: hidratación incremental por defecto y cache de recursos en SSR

Corriste ng update a Angular 22 y ahora la hidratación incremental está activa sin que toques una línea. Si tu app ya usaba provideClientHydration(), el commit "make incremental hydration default behavior" la encendió por ti. Y en la misma versión llegó la otra pieza que faltaba del puzzle SSR: cachear resultados de resource() en el TransferState. - Fuente: Angular v22.0.0 release notes

Las dos cosas resuelven el mismo tipo de problema: la distancia entre lo que el servidor renderiza y lo que el navegador reconstruye. Una ataca el JavaScript que cargas; la otra, las peticiones que repites. Vamos por partes.

Qué cambió exactamente en v22

Antes de v22, la hidratación incremental era opt-in. En v19 llegó como developer preview con withIncrementalHydration(), en v20 se volvió estable, y en v22 se convirtió en el comportamiento por defecto:

// main.ts
import {bootstrapApplication, provideClientHydration} from '@angular/platform-browser';

bootstrapApplication(App, {
  providers: [provideClientHydration()],
});

Con esta única llamada, v22 te da tres cosas: hidratación del DOM, el transfer cache de HttpClient y la hidratación incremental. - Fuente: provideClientHydration API

Detalles que conviene saber:

  • withIncrementalHydration() quedó deprecado. Sigue funcionando en v22, pero la intención oficial es eliminarlo en v24. Si lo tienes, bórralo. - Fuente: withIncrementalHydration API
  • Event replay se activa solo. La hidratación incremental depende de event replay y lo habilita automáticamente. Si tenías withEventReplay() en tu lista de providers, ya puedes quitarlo. - Fuente: Incremental hydration guide
  • ¿Quieres el comportamiento de antes? Existe un opt-out explícito:
// main.ts
import {provideClientHydration, withNoIncrementalHydration} from '@angular/platform-browser';

bootstrapApplication(App, {
  providers: [provideClientHydration(withNoIncrementalHydration())],
});

Si vienes de mi guía de rendering en Angular, esto cambia el mapa: el modelo CSR/SSR/SSG sigue igual, pero el "híbrido" ya no requiere configuración.

Los triggers de hidratación

La hidratación incremental funciona con @defer y triggers de tipo hydrate. Cada trigger define una frontera de hidratación: el servidor renderiza el template principal, pero el navegador no descarga ni hidrata ese bloque hasta que el trigger se dispara.

<!-- dashboard.html -->
@defer (hydrate on interaction) {
  <analytics-panel />
} @placeholder {
  <div>Panel loading…</div>
}

El catálogo completo:

  • hydrate on idle: cuando el navegador está libre. Nuevo en v22: acepta timeout, @defer (hydrate on idle(500)). - Fuente: v22.0.0 release notes
  • hydrate on viewport: cuando el bloque entra en pantalla.
  • hydrate on interaction: click o keydown sobre el elemento.
  • hydrate on hover: mouseover o focusin.
  • hydrate on immediate: inmediatamente después del contenido no diferido.
  • hydrate on timer(500ms): tras un tiempo determinado.
  • hydrate when expr: cuando una expresión se vuelve truthy.
  • hydrate never: nunca; el bloque queda como contenido estático en la carga inicial.

Puedes combinar triggers de carga con triggers de hidratación. En la carga inicial (SSR) manda el hydrate; en navegaciones client-side manda el trigger normal:

<!-- product-list.html -->
@defer (on idle; hydrate on interaction) {
  <filters-panel />
} @placeholder {
  <div>Filters</div>
}

Un detalle que se agradece: antes, un @defer above the fold intercambiaba placeholder por contenido y generaba layout shift. Con hidratación incremental, el servidor renderiza el template principal desde el inicio, así que el contenido está donde tiene que estar y la hidratación no mueve un pixel. La guía oficial lo resume en beneficios cualitativos: bundles iniciales más pequeños que mejoran FID y CLS. Cifras oficiales de LCP/INP no hay publicadas, así que mide en tu app antes de citar números. - Fuente: Incremental hydration guide

Cache de recursos en SSR: la opción id

Ahora la otra mitad. Cuando tu app renderiza en el servidor, un resource() corre su loader para producir el HTML inicial. Y durante la hidratación, el navegador corre el mismo loader otra vez. La misma petición, dos veces. En v22, la solución cabe en una línea:

// user-profile.ts
import {resource, Signal} from '@angular/core';

const userId: Signal<string> = getUserId();

const userResource = resource({
  params: () => ({id: userId()}),
  loader: ({params}) => fetchUser(params),
  id: 'user-unique-id',
});

La opción id es nueva en BaseResourceOptions y estable desde v22.0. Funciona así: en el servidor, el valor resuelto se guarda en el TransferState y se serializa en el HTML inicial. En el cliente, si el dato está en el cache, el resource se inicializa directamente en estado 'resolved' y el loader nunca corre. - Fuente: Resource guide: Caching resource data with SSR

Código real que funciona: el usuario ve el perfil sin flash de "loading" y tu backend recibe una petición menos por visita. Reglas para que no te pegue:

  1. El id debe ser único dentro de tu aplicación e idéntico en servidor y cliente. Si no coinciden, el cache no se encuentra y el loader corre igual.
  2. La advertencia de seguridad es seria: el valor cacheado se serializa en el HTML de la página. Si el HTML puede compartirse o cachearse entre usuarios, no pongas id en resources que cargan datos específicos del usuario que disparó el render. - Fuente: misma fuente

¿Y httpResource? No necesita nada: va por HttpClient, que ya tiene su transfer cache por defecto desde v16. El id es para los loaders custom: fetch(), SDKs de terceros, snapshots de Firestore, cualquier cosa que no pase por HttpClient. - Fuente: SSR guide

Cómo se compone todo

Las piezas encajan así:

  • Route-level render modes deciden qué rutas renderizan en el servidor. La hidratación incremental solo actúa donde hubo SSR: en navegaciones client-side, un @defer con hydrate se comporta como un @defer normal y se renderiza el @placeholder. Por eso sigue siendo obligatorio. - Fuente: Incremental hydration guide
  • Event replay encola los eventos que llegan antes de la hidratación y los repite después. Es lo que hace seguro el patrón hydrate on interaction: el usuario puede hacer click antes de que el bloque esté vivo y el click no se pierde.
  • El cache de id elimina el re-fetch en hidratación; el transfer cache de HttpClient hace lo propio con las peticiones que sí pasan por HttpClient.

Un cambio menor pero que te va a encontrar: provideServerRendering ahora limita las respuestas a 1 MB por defecto con el fetch backend. Si te pasas, error NG02825. Se ajusta con maxResponseBodySize. - Fuente: SSR guide

Checklist de migración v21 → v22

Del changelog y la guía de actualización:

  1. ng update @angular/core@22 @angular/cli@22. Nada más para encender la hidratación incremental si ya tenías provideClientHydration().
  2. Borra withIncrementalHydration() si lo usabas. Deprecado, fuera en v24.
  3. Borra withEventReplay() si lo tenías. Redundante ahora.
  4. Agrega id a tus resource()/rxResource() que quieras cachear en SSR. Recuerda la advertencia de datos por usuario.
  5. Si sirves respuestas mayores a 1 MB en SSR, configura maxResponseBodySize en provideServerRendering.

Tips para producción

  1. Empieza por los componentes pesados de abajo. Mapas, gráficas, editors: son los que más JS cargan y los que el usuario rara vez toca al aterrizar. hydrate on idle o hydrate on viewport y a medir.
  2. Los triggers de interacción son para componentes sobre los que se puede hacer click antes de hidratar. Un botón "Cargar más" es el candidato perfecto: el click se encola y se repite.
  3. Un id por resource, documentado. Seis meses después nadie recordará por qué ese string es 'user-unique-id'. Un comentario de tres palabras ahorra una investigación de tres horas.
  4. Datos por usuario, sin id. El TransferState termina en el HTML. Un carrito de compras cacheado en el HTML compartido es un ticket de soporte esperando a nacer.
  5. Mide FID y CLS antes y después. Los beneficios documentados son cualitativos; tus números, los que valen.

Si aún no leíste cómo encaja esto en el panorama completo de rendering, empieza por CSR, SSR y SSG en Angular y vuelve. Y si quieres el resto de las novedades de la versión, ahí está mi repaso de Angular 22.

¿Cuántos kilobytes de JS te ahorraría hidratar por demanda tu página más pesada? Cuéntame en los comentarios.


> Más posts