Un contrato de hidratación para cada isla de Astro
Una isla no debería existir solo porque un componente puede usar JavaScript. En Mintec asignamos a cada interacción un contrato de hidratación con gatillo, presupuesto, fallback y dueño antes de añadir client:load.
Un contrato de hidratación para cada isla de Astro
Una isla de Astro es deuda de UX: el navegador la paga al descargar, analizar y ejecutar JavaScript. Antes de añadir client:load, en Mintec pedimos un contrato de hidratación: tarea, gatillo, límite de peso, HTML previo, fallo esperado y dueño. Convierte una decisión invisible en una revisión concreta.
Astro puede entregar HTML útil sin runtime de cliente. La ventaja se pierde cuando las islas se acumulan por inercia: calendario, selector con búsqueda, galería, chat y experimentación, todas en client:load. Por separado parecen menores; juntas compiten al inicio por red y por el hilo principal.
La documentación de Astro separa client:load, client:idle, client:visible, client:media y client:only.[1] La pregunta no es cuál hace funcionar el componente, sino cuál permite la tarea sin cobrarle al resto de la página.
Una directiva no es una optimización automática
client:visible suele presentarse como la respuesta elegante a cualquier widget pesado. No lo es. Si el usuario llega a una calculadora arriba del fold y toca el primer input antes de que la isla haya hidratado, el sitio se siente roto aunque Lighthouse se vea bien. Si un menú móvil se hidrata en idle y una persona abre el menú de inmediato, el presupuesto técnico se convirtió en fricción real.
El error inverso usa client:load para un player, mapa, carrusel o reserva que está más abajo. El coste llega al inicio y el valor tarde. Cada isla necesita una ficha breve que sobreviva a quien instaló el componente.
| Campo del contrato | Pregunta que obliga a responder | Ejemplo aceptable |
|---|---|---|
| Tarea del usuario | ¿Qué puede completar la persona gracias a esta isla? | Comparar planes y calcular el ahorro estimado |
| Gatillo | ¿Por qué debe hidratar en carga, idle, visibilidad o media query? | client:visible porque el mapa empieza 1,400 px después del hero |
| Presupuesto | ¿Cuánto JavaScript puede sumar, incluido el proveedor? | 28 KB gzip para el selector, 0 KB en la ruta editorial |
| Estado previo | ¿Qué ve y puede hacer el usuario antes de que llegue JS? | Tabla de planes legible y CTA funcional |
| Fallo | ¿Qué pasa si el bundle, una API o un script de tercero no llega? | Formulario HTML y enlace a agenda; no spinner eterno |
| Accesibilidad | ¿Cómo cambia foco, teclado, movimiento o anuncio de estado? | Diálogo con foco gestionado y salida con Escape |
| Dueño | ¿Quién aprueba una subida de peso o cambio de gatillo? | Responsable del feature y revisión de frontend |
La clasificación que usamos antes de tocar un componente
En una revisión de página, empezamos separando los componentes por la urgencia de la tarea, no por el framework que los creó. El resultado no es una lista de directivas favoritas; es una decisión de carga que alguien pueda defender.
| Tipo de interacción | Decisión de Mintec | Motivo |
|---|---|---|
| Navegación, acordeón informativo, CTA y contenido filtrable simple | HTML y CSS primero; añadir JS solo si resuelve una necesidad real | La ruta básica debe seguir disponible con latencia alta o un fallo de script |
| Menú móvil, validación que evita un error costoso, control que está visible al cargar | client:load con un límite de peso estricto | La persona puede necesitarlo antes de que exista un momento de idle confiable |
| Comparador opcional, buscador secundario, widget de preferencias | client:idle con timeout cuando corresponde | Es útil, pero no debe bloquear la primera interacción de la ruta |
| Mapa, player, galería con zoom, calculadora dentro de una sección posterior | client:visible | El coste llega cerca del momento en que el usuario puede usarlo |
| Variante que solo existe a partir de un breakpoint | client:media | Evita descargar comportamiento de escritorio en un dispositivo que no lo muestra |
| Componente que no puede renderizar en servidor | client:only como excepción documentada | Se acepta el coste de no tener HTML previo; requiere fallback explícito |
Astro documenta estos gatillos, pero la política de elegirlos es nuestra. En Mintec desconfiamos de una interfaz que exige client:only para contenido que debería ser comprensible como HTML. A veces es inevitable, por ejemplo, una visualización que depende de WebGL. Muchas veces es señal de que el componente está haciendo trabajo de presentación, contenido y estado a la vez.
Un Server Island resuelve otro problema. Puede traer stock, precio o una recomendación dinámica sin convertir toda la ruta en una aplicación de cliente. En nuestro análisis de arquitectura de contenido a escala explicamos por qué diferir HTML de servidor no equivale a hidratar JavaScript. Cuando un componente necesita datos frescos y además responde a interacción, el contrato debe declarar las dos capas: cuándo aparece el HTML dinámico y cuándo llega el código de cliente.
El contrato deja claro qué no debe ser una isla
La revisión más rentable no busca dónde añadir una directiva. Busca qué puede volver a ser documento.
Un bloque de preguntas frecuentes que abre y cierra respuestas quizá puede ser un <details> nativo. Una transición entre rutas puede vivir en CSS o en View Transitions antes de introducir una biblioteca de animación. Un selector de planes puede comenzar como enlaces que preservan la URL y volverse interactivo solo como mejora progresiva. En esos casos, el contrato no dice client:idle: dice “no hidratar”.
Esa postura también reduce superficie de seguridad. Cada isla que muta el DOM, consume HTML de un CMS o carga un proveedor crea una nueva frontera. Nuestro trabajo con Trusted Types en Astro y Next.js muestra cómo hacer revisables esas fronteras; el contrato de hidratación añade la pregunta anterior: si no aporta una tarea clara, ¿por qué está corriendo en el navegador?
Para secciones largas, el CSS content-visibility puede reducir trabajo de renderizado fuera de pantalla. MDN explica que permite al navegador omitir layout y pintura de contenido hasta que sea necesario.[2] Eso no sustituye una directiva de Astro: sigue existiendo el coste del JavaScript si hidratamos una isla. Pero evita mezclar dos problemas. Primero decidimos si el componente necesita código de cliente. Después decidimos cuánto trabajo visual puede diferir el navegador.
Un ejemplo: el mismo bloque, dos decisiones muy distintas
Imagina una página de servicio con dos elementos: una calculadora de inversión y un mapa de cobertura.
La calculadora aparece bajo el titular y forma parte de la promesa principal. Puede ser el primer elemento que alguien usa después de leer dos líneas. Su contrato permite client:load, pero exige un fallback: inputs HTML, una fórmula visible y un CTA que no dependa del resultado dinámico. También define un presupuesto: si la librería de gráficos hace que el feature se vuelva pesado, se cambia por una salida textual o se carga el gráfico después de la primera respuesta.
El mapa está después de los casos de uso. Aporta confianza, pero no desbloquea la decisión inicial. Su contrato usa client:visible, renderiza una imagen estática accesible como estado previo y conserva una lista de ciudades fuera del mapa. Si el proveedor falla, el visitante no pierde la información ni queda frente a un bloque gris.
Los dos componentes pueden usar React, Preact, Svelte o un Web Component. Eso no cambia el contrato. Cambia la implementación, no la responsabilidad de explicar por qué ese código debe llegar en ese momento.
El presupuesto tiene que vivir en la revisión, no en una slide
Medir el bundle total una vez al trimestre llega tarde. La isla nueva casi siempre se aprueba porque el sitio todavía carga “bien”. Preferimos tener límites que se puedan revisar en el pull request:
# hydration-contracts.yml pricing-calculator: trigger: client:load max_gzip_kb: 32 fallback: native-form owner: growth-web coverage-map: trigger: client:visible max_gzip_kb: 24 fallback: static-image-and-list owner: web-platform
El archivo no necesita automatizar todo desde el primer día. Puede empezar como una convención de revisión y convertirse después en una comprobación de CI que compare el tamaño de cada entrada. La parte importante es que una subida de 10 KB deje de ser un detalle técnico sin contexto. Ahora hay que decidir si sigue cabiendo en la tarea, el gatillo y el fallback pactados.
Este enfoque complementa el presupuesto de rendimiento para sitios con multimedia. Allí el foco es red, video, memoria y Core Web Vitals a nivel de página. El contrato trabaja a nivel de componente: identifica quién está gastando el presupuesto y qué experiencia se obtiene a cambio.
La auditoría que haríamos esta semana
No hace falta reescribir una página entera para empezar. Abrimos DevTools, listamos las islas y hacemos cinco preguntas por cada una:
- ¿Cuál es la tarea concreta que deja completar?
- ¿El usuario puede necesitarla antes de que el componente llegue a visible o idle?
- ¿Qué HTML útil queda si se corta JavaScript o el proveedor?
- ¿Qué peso agrega realmente, dependencias incluidas?
- ¿Quién debe aprobar que ese peso o ese gatillo cambie?
Las respuestas suelen encontrar tres oportunidades rápidas: componentes que pueden volver a HTML, widgets secundarios que pueden pasar a client:visible, y componentes importantes que necesitan un fallback más honesto. No prometemos que cada página termine con menos islas. Prometemos que ninguna isla quedará sin motivo, sin límite y sin responsable.
Eso es lo que significa usar Astro como arquitectura y no solo como generador de sitios: preservar el HTML rápido por defecto y gastar JavaScript donde el usuario realmente obtiene algo a cambio.
Sources
[1] https://docs.astro.build/en/reference/directives-reference — Astro: Client Directives [2] https://developer.mozilla.org/en-US/docs/Web/CSS/content-visibility — MDN: content-visibility
Preguntas Frecuentes
¿Qué es un contrato de hidratación en Astro?
Es una decisión documentada para un componente interactivo: qué necesita JavaScript, cuándo puede descargarse, qué límite de peso tiene, qué HTML ve el usuario antes de hidratar y quién responde por sus regresiones.
¿Cuándo conviene usar client:visible?
Cuando la interacción está fuera del primer viewport y esperar a que el componente se acerque a la pantalla no impide que el usuario complete una tarea. Players de video, mapas, galerías y widgets de prueba suelen encajar aquí.
¿Un Server Island reemplaza una isla con client:visible?
No. Un Server Island difiere o personaliza HTML en el servidor. client:visible decide cuándo el navegador descarga e inicia JavaScript. Un componente puede necesitar una, ambas o ninguna de las dos cosas.



