Islas, hidratación y scope en el output
Cómo el compilador traduce lo que escribes en el marcado del navegador: las directivas client como contrato que declara una isla, el elemento astro-island que envuelve cada componente hidratado con sus props serializadas y la URL de su renderizador, y el hash de ámbito data-astro-cid que aísla los estilos. Todo lo que el runtime hace en el cliente empieza como una decisión del compilador.
Las islas y el scope de estilos suelen contarse desde el runtime: el navegador hidrata este componente, este estilo no se derrama. Pero ninguna de esas dos cosas empieza en el navegador; empiezan en el compilador, que al ver una directiva client: o un bloque <style> toma decisiones y las estampa en el HTML como marcas concretas. El elemento <astro-island> que envuelve una isla y el atributo data-astro-cid que aísla un estilo son salida del compilador, no invenciones del cliente. Leerlas en el output cierra el círculo entre lo que escribes y lo que corre.
- Entender las directivas
client:como el contrato que declara una isla. - Leer el elemento
<astro-island>y las props serializadas del output. - Relacionar la isla con la URL de su renderizador y su estrategia de carga.
- Reconocer el hash
data-astro-cidcomo la marca de ámbito de los estilos.
La directiva client como contrato
Cuando escribes un componente de framework con una directiva client:, no estás llamando a una función: estás firmando un contrato con el compilador. La directiva client:load, client:visible, client:idle o client:only es una anotación que el compilador detecta durante la transformación y que cambia por completo lo que emite para ese nodo. Sin directiva, el componente se renderiza en el servidor y se sirve como HTML muerto, sin un byte de JavaScript. Con directiva, el compilador lo marca como isla y planifica su hidratación.
---
import Contador from '../components/Contador.jsx';
---
<Contador client:visible initial={3} />
Esa sola palabra, client:visible, dispara tres decisiones en el compilador: registrar el componente en la lista de hydratedComponents que vimos en el pipeline, recordar qué estrategia de carga pediste —cargar al ser visible—, y envolver la salida en un elemento especial que el runtime sabrá reconocer. La directiva es, por tanto, el punto donde tu intención declarativa se traduce en un plan de hidratación. No hidratas tú; declaras que ese nodo quiere hidratarse, y el compilador prepara todo para que ocurra.
La misma línea de código produce dos salidas radicalmente distintas según lleve o no la directiva. El contraste se ve mejor en el HTML emitido en cada caso:
<!-- SIN directiva: HTML plano, cero JavaScript, no revive -->
<button>3</button>
<!-- CON client:visible: envuelto y listo para hidratarse -->
<astro-island component-url="..." client="visible" props="...">
<button>3</button>
</astro-island>
Sin client:, el compilador renderiza el componente en el servidor y estampa su HTML plano, sin envoltorio ni referencia a ningún módulo de cliente. Con client:, ese mismo HTML aparece envuelto y acompañado de las rutas y las props que permitirán revivirlo. La directiva no cambia lo que el componente muestra; cambia si ese componente seguirá vivo en el navegador.
client:load
Hidrata en cuanto la pagina carga. El compilador marca la isla como prioritaria.
client:visible
Difiere la hidratacion hasta que la isla entra en pantalla, via IntersectionObserver.
client:idle
Espera a que el navegador este ocioso. La isla no compite con el trabajo urgente.
client:only
No renderiza en el servidor: la isla nace y vive solo en el cliente.
astro-island: el envoltorio en el HTML
La marca que el compilador estampa alrededor de una isla es un elemento personalizado, <astro-island>, y leerlo en el HTML final revela toda la mecánica. Ese elemento no lo escribes tú: lo genera el compilador para cada componente con directiva, y lleva en sus atributos todo lo que el runtime del cliente necesita para revivir el componente en el momento acordado.
<astro-island
uid="Z1qs8x"
component-url="/_astro/Contador.abc123.js"
renderer-url="/_astro/client.react.def456.js"
props='{"initial":{"type":"number","value":3}}'
client="visible"
ssr>
<button>3</button>
</astro-island>
Cada atributo es una pieza del rompecabezas, y conviene leerlos uno a uno porque juntos son el plan de hidratación completo:
uid: un identificador único de esta isla en la página, para que el runtime no confunda unas con otras.component-url: la ruta al módulo de tu componente ya empaquetado.renderer-url: la ruta al adaptador del framework —React, Vue, Svelte— que sabe hidratarlo.client: la estrategia grabada de la directiva; aquí,visible.props: tus props serializadas en un JSON con tipos.ssr: la señal de que el HTML de dentro se renderizó en el servidor.
Dentro del elemento va el HTML renderizado en el servidor, el que el visitante ve antes de que llegue ni un byte de JavaScript. El runtime lee estos atributos, importa las dos URLs cuando la estrategia lo indica y sustituye ese HTML por el componente vivo. Nada de eso lo decide el cliente por su cuenta: todo estaba escrito de antemano por el compilador en los atributos del envoltorio.
Fíjate en la economía del diseño: dos islas del mismo componente en una página comparten component-url y renderer-url —el navegador descarga cada módulo una sola vez— pero difieren en su uid y en sus props. El compilador no duplica código; duplica solo el contrato mínimo que distingue una instancia de otra. Por eso una página con veinte tarjetas interactivas no envía veinte copias del componente, sino una y veinte pequeños sobres de datos. La granularidad de la hidratación —qué se carga, cuándo y para qué instancia— está enteramente decidida en estos atributos que el compilador estampó.
Fíjate en que props no es un JSON ingenuo: cada valor lleva su tipo. Eso existe porque el HTML solo transporta texto, pero una isla puede recibir un Date, un Map, un Set o un BigInt que un JSON.stringify normal destrozaría. El compilador serializa con un esquema que preserva el tipo, y el runtime lo reconstruye al hidratar. Por eso una prop compleja sobrevive el viaje del servidor al cliente sin que tú hagas nada.
flowchart TD DIR[directiva client en tu componente] --> COMP[compilador detecta la isla] COMP --> META[registra hydratedComponents] COMP --> WRAP[emite el elemento astro-island] WRAP --> HTML[html del servidor dentro del envoltorio] WRAP --> ATTR[atributos component-url renderer-url y props] ATTR --> RT[runtime hidrata segun la estrategia]
El <astro-island> no es un nombre inventado que el runtime busca con un selector: es un elemento personalizado registrado de verdad en el navegador, con su ciclo de vida propio. Cuando el navegador construye el DOM y encuentra la etiqueta, dispara el connectedCallback del elemento, y ese callback es el que arranca la maquinaria de hidratación según la estrategia grabada en el atributo client. Por eso el mecanismo funciona sin un script coordinador que recorra la página: cada isla se despierta sola cuando el navegador la instancia. El compilador se apoya en una capacidad estándar de la plataforma, no en un truco propio.
El hash de ámbito para los estilos
La otra gran marca que el compilador estampa nace de los bloques <style>. Al ver estilos en un componente, calcula un hash determinista a partir de la identidad del archivo y lo usa para dos cosas a la vez: sella cada elemento de la plantilla con el atributo data-astro-cid-<hash> y reescribe cada selector del bloque para que exija esa misma marca. El estilo queda confinado no por una barrera, sino por una condición que solo se cumple dentro del componente.
<!-- lo que emite el compilador para un componente con estilos -->
<article class="tarjeta" data-astro-cid-hbhonbnp>Contenido</article>
<style>.tarjeta[data-astro-cid-hbhonbnp]{color:#7c3aed}</style>
Que el hash sea determinista es deliberado: mientras la identidad del componente no cambie, la marca es idéntica entre builds, lo que estabiliza las hojas de estilo y favorece la caché. Este mecanismo tiene su nivel dedicado en la guía; lo que importa aquí es dónde nace: en el compilador, en el mismo tramo de transformación donde se marcan las islas.
Cómo se ancla esa marca en el selector no está grabado en piedra: el compilador obedece a la opción scopedStyleStrategy de la configuración, que decide si la condición se añade como selector de atributo, como clase o envuelta en :where para no sumar especificidad. Es un ejemplo perfecto de que el output no es fijo, sino el resultado de una política que tú puedes ajustar y que el compilador aplica de forma uniforme a cada componente.
Islas y scope resultan ser, en el fondo, la misma clase de operación: en ambos casos el compilador toma una intención que en tu .astro era implícita —este componente es interactivo, este estilo es local— y la convierte en una marca explícita en el HTML —un elemento <astro-island>, un atributo data-astro-cid— que otro programa leerá después. Son las dos huellas que el compilador deja en el output, y ambas son legibles a simple vista si sabes qué buscar.
Un error común es creer que importar un componente de framework ya lo vuelve interactivo. No: sin una directiva client:, el compilador lo renderiza en el servidor y lo sirve como HTML inerte, sin <astro-island> ni JavaScript. Si tu componente no responde a los clics, lo primero que hay que mirar en el output es si existe el envoltorio <astro-island>. Si no está, falta la directiva, y ninguna cantidad de depuración en el cliente lo arreglará.
La directiva client:only no produce un <astro-island> con HTML de servidor dentro, porque el componente no se renderiza en el servidor: por eso el compilador lo registra en una lista separada, clientOnlyComponents, en vez de en hydratedComponents. Y como no puede inferir el framework a partir de un render que no ocurre, esta directiva exige que le digas cuál es —client:only="react"—. Verlo en el output es instructivo: el envoltorio existe, pero su interior está vacío hasta que el cliente lo puebla. Es la única isla que nace sin cuerpo.
Vale la pena elevar la mirada y ver qué clase de artefacto es en realidad ese <astro-island>. No es un componente ni un contenedor decorativo: es un mensaje, un paquete de instrucciones que un programa —el compilador, que corrió en tu máquina o en el build— le deja a otro programa —el runtime, que correrá en el navegador de un desconocido dentro de semanas— usando el único canal que comparten, que es el propio HTML. Piénsalo: esos dos programas nunca se ejecutan a la vez, nunca comparten memoria, nunca se llaman entre sí. Y sin embargo tienen que coordinarse con precisión total —qué módulo importar, qué renderizador usar, qué props reconstruir, cuándo hidratar—. La solución de Astro es elegante hasta lo humilde: escribir esa coordinación en atributos de un elemento HTML, el formato más viejo y más universal de la web, convertido aquí en un protocolo de mensajería diferida entre el pasado del build y el futuro de la visita. Las props tipadas en un JSON, la URL del componente, la palabra visible grabada en un atributo: todo eso es un contrato serializado, congelado en texto, esperando a que alguien lo lea. Y el hash de ámbito juega el mismo juego en el terreno del estilo: es un token que el compilador estampa en dos sitios —el nodo y el selector— para que, mucho después, el navegador pueda reconstruir una frontera que en el .astro original era solo la implícita del archivo. Cuando entiendes que el HTML generado no es un resultado sino un mensaje entre dos tiempos, dejas de leer el output como ruido de máquina y empiezas a leerlo como lo que es: la carta que el compilador le escribe al runtime, redactada en el único idioma que ambos, separados por el abismo del despliegue, saben leer.
- Añade a una página un componente de framework con
client:visibley localiza en el HTML generado el elemento<astro-island>que lo envuelve. - Examina sus atributos: identifica
component-url,renderer-url,clienty el JSON tipado deprops, y explica qué hace el runtime con cada uno. - Quita la directiva
client:y vuelve a compilar: comprueba que el<astro-island>desaparece y el componente queda como HTML inerte. - Añade un bloque
<style>al componente, busca en el output el atributodata-astro-ciden los nodos y confirma que el mismo hash aparece en los selectores.