astro-hidratacion-directivas-client

Hidratación en Astro

  • 6 min

En el artículo anterior vimos las islas de interactividad, pero nos quedamos a medias en lo mejor (un poquito de cliffhanger, you know… 😉).

Renderizamos un componente de React (un contador), se veía perfecto, pero al hacer clic no hacía absolutamente nada.

El motivo es que Astro, por defecto, envía HTML limpio y pelado. Es decir, renderiza el componente en el servidor, saca una “foto” del estado inicial, y eso es lo que envía al navegador. Elimina todo el JavaScript para ahorrar peso.

Para que el componente “reviva” y responda a nuestras interacciones, necesitamos Hidratarlo 💧💧.

¿Qué es la hidratación?

En el contexto del desarrollo web, la Hidratación es el proceso mediante el cual el código JavaScript del navegador “se engancha” al HTML estático que vino del servidor para añadirle la interactividad (event listeners, gestión de estado, etc.).

La mayoría de frameworks (Next.js, Nuxt) hidratan toda la página en cuanto carga. Astro es diferente. Astro permite la Hidratación Parcial.

Podemos decirle:

Astro, quiero que esta página sea 90% HTML estático, pero quiero que este componente específico cargue su JavaScript y sea interactivo

Para ello usamos las directivas client:.

Las directivas de hidratación

Estas directivas son atributos especiales que se añaden al componente cuando lo usamos dentro de un archivo .astro.

DirectivaCarga JSPrioridadCaso de Uso
(ninguna)NuncaN/AContenido estático, texto, imágenes.
client:loadInmediatamenteAltaMenús, elementos críticos.
client:idleCuando hay tiempoMediaWidgets, interactividad secundaria.
client:visibleAl hacer scrollBajaGalerías, Footer, elementos pesados.
client:mediaSegún pantallaVariableMenús móviles, Sidebars.
client:onlyInmediatamenteAltaAcceso a window, localStorage.

Las directivas client:* no se ponen dentro del código del componente (ej: dentro del .jsx), sino en el momento de usarlo en la plantilla .astro.

Vamos a repasar una a una, ordenadas por prioridad de carga.

client:load (prioridad alta)

Esta es la directiva más agresiva. Le dice a Astro: “Carga e hidrata el JavaScript de este componente inmediatamente, en cuanto cargue la página”.

<Contador client:load />
Copied!
  • Para elementos que el usuario necesita usar inmediatamente al abrir la web.
  • Ejemplos: La barra de navegación (si tiene menú desplegable móvil), un banner de login, o el elemento principal de una SPA.

Coste: Impacta en el TTI (Time to Interactive) inicial, ya que el navegador debe descargar y ejecutar el JS antes de terminar de procesar.

client:idle (prioridad media)

Aquí es donde Astro empieza a brillar. Esta directiva le dice al navegador: “Espera a que termines de cargar todo lo importante, y cuando tengas un momento libre (estés ocioso), hidrata este componente”.

Técnicamente, utiliza requestIdleCallback para aprovechar los tiempos muertos del hilo principal.

<Contador client:idle />
Copied!
  • Para elementos interactivos que no son críticos para la primera impresión.
  • Ejemplos: Un botón de “Me gusta”, un widget de chat de soporte, o un buscador en el pie de página.

client:visible (prioridad bajo demanda)

Esta es mi favorita y la que más recursos ahorra. El componente no carga su JavaScript hasta que el usuario hace scroll y el componente entra en la pantalla.

Técnicamente, utiliza un IntersectionObserver interno. Si el componente está en el footer y el usuario nunca hace scroll hasta abajo, ese JavaScript nunca se descarga.

<Contador client:visible />
Copied!
  • Para componentes pesados que están “below the fold” (fuera de la primera pantalla).
  • Ejemplos: Carruseles de imágenes, galerías interactivas, formularios de contacto al final de una landing page.

Puedes combinarlo con un margen de seguridad: client:visible={{ rootMargin: "200px" }} para que empiece a cargar un poco antes de que aparezca en pantalla, haciendo la transición invisible al usuario.

client:media (condicional css)

Esta directiva hidrata el componente solo si se cumple una Media Query de CSS. Es extremadamente útil para patrones de diseño responsivo.

<Sidebar client:media="(min-width: 768px)" />

<MenuMovil client:media="(max-width: 767px)" />
Copied!

Si estoy en un móvil, el componente Sidebar se renderizará como HTML estático (se verá), pero su lógica JS (si es muy pesada) no se descargará.

client:only (sin SSR)

Esta es la excepción a la regla. Todas las anteriores renderizan HTML en el servidor (SSR) y luego hidratan. client:only se salta el renderizado en servidor.

El componente no existe en el HTML inicial. El navegador descarga el JS y renderiza el componente desde cero en el cliente.

<Contador client:only="react" />
Copied!

¿Cuándo usarla?

  • Cuando el componente usa APIs que solo existen en el navegador (window, localStorage, document) y fallaría si intentas ejecutarlo en Node.js durante el build.
  • Para componentes que dependen totalmente de datos privados del usuario (como un panel logueado) y no quieres renderizar nada genérico.

Es obligatorio especificar el framework (ej: client:only="react" o ="vue") porque Astro no sabe qué adaptador usar durante la compilación del servidor.

El procedo de hidratación

Ahora que sabemos esto, volvamos a nuestro ejemplo. Si cambiamos el código de la lección anterior:

---
import Contador from '../components/Contador.tsx';
---

<Contador />

<Contador client:load />
Copied!

Al hacer esto, Astro descargará un pequeño archivo JS que contiene solo la lógica de React necesaria para ese contador. El resto de la página sigue siendo HTML puro.

Esta arquitectura granular es lo que permite a Astro obtener puntuaciones de 100/100 en Lighthouse incluso usando frameworks pesados.

Ahora tenemos islas interactivas. Pero surge un nuevo problema. Si tengo un Contador en React en la cabecera y un Visualizador en Svelte en el pie de página… ¿Cómo hago para que compartan datos? Si pulso el botón en React, ¿cómo se entera Svelte?

En las SPAs tradicionales usaríamos Redux o Context. En Astro, como las islas viven separadas, necesitamos otra estrategia. En el próximo artículo hablaremos de Nano Stores y el estado compartido.