Qué es una isla: modelo mental y anatomía
Una isla no es un componente cualquiera, sino una frontera de hidratación: la región donde un componente de framework, renderizado a HTML en el servidor, vuelve a la vida en el cliente por separado. Ver qué emite Astro —el elemento astro-island, sus props serializadas y el runtime diminuto que orquesta el arranque— convierte la metáfora del archipiélago en un modelo mecánico preciso con el que razonar y depurar.
En el nivel 1 conociste el archipiélago: un mar de HTML con islas interactivas. Ahora bajamos de la metáfora al mecanismo. Una isla, definida con precisión, es una frontera de hidratación: la porción de la página donde un componente de framework, renderizado a HTML en el servidor, vuelve a la vida en el cliente como aplicación autónoma. Entender qué emite Astro exactamente —y qué cruza esa frontera— es lo que te permite predecir el coste y depurar cuando algo no hidrata.
- Definir la isla como frontera de hidratación, no como cualquier componente.
- Leer el elemento
<astro-island>y la carga útil que emite el servidor. - Entender cómo se serializan las props del servidor al cliente.
- Situar el runtime diminuto que decide cuándo y cómo hidratar.
Primero el HTML, después la isla
Toda isla vive en dos tiempos. En el primero, durante el build o la petición, Astro ejecuta el componente de framework en el servidor y produce su HTML: el mismo marcado que el usuario vería sin JavaScript. En el segundo, ya en el navegador, ese HTML inerte se hidrata —se le adjuntan escuchadores de eventos y estado— y pasa a comportarse como el componente vivo que escribiste. Entre ambos tiempos, lo que hay en la página es HTML normal y corriente.
Esta secuencia es el corazón del modelo mental. Un .astro se renderiza a HTML y desaparece sin dejar rastro de runtime. Un componente de framework con una directiva client:* también se renderiza a HTML, pero deja una semilla: la información necesaria para que el navegador lo reconstruya. Esa semilla es lo que distingue una isla de un fragmento estático, y es literal: un elemento en el DOM que puedes inspeccionar.
El orden —HTML primero, JavaScript después— no es un detalle de implementación, sino mejora progresiva elevada a principio de arquitectura. La página es útil antes de que llegue una línea de código de isla; el JavaScript solo la mejora. Por eso una isla que tarda en hidratar degrada con elegancia en lugar de romperse: mientras espera, el usuario sigue viendo su HTML.
Hidratar reutiliza el HTML que ya llegó: el framework recorre el marcado existente, lo empareja con su árbol virtual y le engancha la interactividad, sin repintar. Por eso el servidor y el cliente deben producir el mismo HTML; si divergen, el framework avisa de un desajuste de hidratación. La excepción es client:only, que no hidrata nada porque no hubo HTML de servidor: renderiza desde cero en el cliente.
Anatomía de <astro-island>
La semilla tiene nombre: <astro-island>, un elemento personalizado que Astro envuelve alrededor del HTML del componente. No es magia ni sintaxis oculta; es HTML que aparece en el documento servido. Simplificado, se ve así:
<astro-island
uid="Z1q9x"
component-url="/_astro/Contador.abc123.js"
component-export="default"
renderer-url="/_astro/client.react.def456.js"
props='{"inicio":[0,42]}'
client="load"
ssr="">
<button>Cuenta: 42</button>
</astro-island>
Cada atributo es una pieza del arranque. El HTML hijo —el botón— es el resultado del render de servidor, ya visible y funcional como marcado antes de que llegue un solo byte de JavaScript de la isla. Nada de esto lo escribes tú: lo genera el compilador a partir de un simple <Contador client:load />. Conviene fijar el vocabulario, porque son los atributos que leerás al depurar:
uid— identificador único de la isla dentro de la página.component-url— el módulo del componente, cargado de forma diferida.renderer-url— el adaptador del framework que sabe hidratarlo.client— la directiva que decide el momento de la hidratación.props— los datos del servidor, serializados como texto.
Cuando pasas props desde el .astro, Astro las recoge, las serializa y las deposita en ese atributo props para que el cliente las recupere al hidratar. El [0,42] del ejemplo es el resultado de haber pasado inicio={42}:
---
import Contador from '../components/Contador.jsx';
const inicio = 42;
---
<Contador inicio={inicio} client:load />
Por qué un elemento personalizado
Astro podría haber orquestado la hidratación con un script que buscara nodos por una clase, pero eligió un custom element por una razón de fondo: el navegador le avisa solo. Cuando un <astro-island> entra en el DOM —también si lo inyecta una navegación con view transitions— su connectedCallback se dispara sin que nadie tenga que reescanear la página. La hidratación se vuelve un comportamiento del propio elemento, no una tarea global que relanzar. Es la misma elegancia que persigue toda la arquitectura: que cada isla se gobierne a sí misma.
Una directiva client:* en un componente .astro no crea una isla: los .astro no se hidratan, porque no tienen runtime que reanimar. Las islas son, por definición, componentes de React, Vue, Svelte, Solid o Preact. Si necesitas interactividad en algo escrito en .astro, la opción es un <script> propio de la página, o extraer la parte interactiva a un componente de framework y marcarla como isla.
La frontera: qué cruza y qué no
El atributo props define qué puede cruzar del servidor al cliente. Astro serializa las props a una cadena que viaja en el HTML, y para ello usa un formato propio más rico que JSON: además de objetos, arrays, cadenas y números, sabe transportar Date, Map, Set, BigInt, RegExp y URL, codificados con una etiqueta que el cliente vuelve a decodificar. Ese formato es el que produce el array [0,42]: un valor etiquetado, no un número suelto.
Lo que no cruza es tan importante como lo que sí. Las funciones no se serializan: no puedes pasar un callback como prop a una isla y esperar que funcione en el cliente, porque una función no tiene representación en texto. Tampoco cruzan las instancias de clase con métodos. La frontera es un cristal: deja pasar datos, no comportamiento. El comportamiento vive en el código del componente, que se carga aparte por component-url.
flowchart TB
COMP[componente con directiva client] --> SSR[servidor renderiza a HTML]
SSR --> WRAP[envuelve en astro-island]
WRAP --> SER[serializa props a texto]
SER --> DOC[documento HTML estatico]
DOC --> RT[runtime define el custom element]
RT --> WHEN{la directiva decide cuando}
WHEN --> IMP[importa renderer y componente]
IMP --> HYD[hidrata sobre el HTML existente]
style DOC fill:#a6e3a1,color:#11111b
style HYD fill:#89b4fa,color:#11111b
style COMP fill:#f9e2af,color:#11111bComo props es un atributo del documento servido, todo lo que pasas a una isla engorda el HTML de la página, se hidrate o no. Pasar un array de mil objetos “por si acaso” añade esos kilobytes a cada respuesta. La disciplina es pasar a la isla el mínimo que necesita para arrancar y dejar que ella pida el resto —a un endpoint, a una API— si de verdad lo requiere. La frontera es barata para datos pequeños y cara para volcados grandes.
El runtime que orquesta
Falta la pieza que da vida a todo: un script diminuto —del orden de un kilobyte— que Astro inserta una sola vez por página y que define el elemento <astro-island> como custom element. Ese runtime no es un framework; es un orquestador. Su trabajo es leer los atributos de cada isla, montar el disparador que corresponde a su directiva y, cuando ese disparador se cumple, importar de forma diferida el renderer y el componente para ejecutar la hidratación.
El ciclo completo de una isla, de principio a fin, encadena siete pasos:
- Escribes el componente de framework con una directiva
client:*. - El servidor lo renderiza a HTML durante el build o la petición.
- Astro lo envuelve en
<astro-island>y serializa sus props. - El documento viaja al navegador, ya visible sin JavaScript.
- El runtime define el custom element y arma el disparador.
- La directiva se cumple e importa el renderer y el componente.
- El framework hidrata sobre el HTML existente y la isla cobra vida.
La semilla
El elemento <astro-island> con sus atributos y el HTML de servidor dentro: todo lo necesario para reconstruir la isla.
El código
component-url y renderer-url apuntan a módulos que se importan solo cuando la isla va a hidratar, nunca antes.
El orquestador
Un runtime diminuto, uno por página, que define el custom element y arma el disparador de cada directiva.
La directiva
El atributo client traduce tu client:* en una política de arranque: ya, en tiempo libre, al verse o según media.
Que el import del framework sea diferido es la clave económica del modelo: hasta que la directiva no se cumple, el navegador no descarga ni ejecuta el código de la isla. Diez islas client:visible que nunca entran en pantalla cuestan diez elementos en el DOM y cero kilobytes de framework ejecutados. El HTML ya sirvió su función; el JavaScript es estrictamente opcional y bajo demanda.
No necesitas creer nada de esto a ciegas. Abre el inspector de elementos y busca <astro-island> en el árbol del DOM: verás sus atributos tal cual y el HTML de servidor como hijo. Si filtras la pestaña de red por el nombre del componente, comprobarás que su módulo solo se descarga en el instante en que la directiva lo dispara. Depurar una isla que no reacciona empieza casi siempre ahí: confirmar que el elemento existe, que su client es el que esperabas y que su módulo llegó.
Cuesta soltar la idea de que una isla es “un componente interactivo”, porque casi lo es. Pero el modelo mental que de verdad rinde es otro: una isla es una frontera trazada sobre la página, y todo lo interesante ocurre por lo que esa frontera deja pasar en cada dirección. Hacia el navegador cruza HTML terminado —visible, indexable, útil sin JavaScript— y una semilla de datos serializados; nunca cruza comportamiento, porque las funciones no tienen forma en texto. Del otro lado, el código del componente espera en un módulo que solo se importa cuando la directiva lo autoriza. Pensar así reordena tus intuiciones de golpe. Deja de sorprenderte que un callback pasado como prop no funcione: no cruzó la frontera. Deja de temer las islas que no se ven: no gastan porque su frontera no se ha abierto. Y entiendes que “cero JS por defecto” no es una optimización que Astro aplica encima, sino la geometría misma del sistema: fuera de las fronteras no hay islas, y donde no hay isla, sencillamente nunca hubo código que enviar. La frontera de hidratación es la unidad atómica del rendimiento en Astro; aprender a trazarla con criterio es casi todo el oficio del resto del nivel.
- Crea un componente de framework mínimo —un contador— y úsalo con
client:loaden una página. - Abre el HTML servido y localiza el elemento
<astro-island>: identificacomponent-url,renderer-url,clientyprops. - Pasa una prop que sea una
Datey observa cómo se serializa en el atributoprops; compárala con pasar un número suelto. - Intenta pasar una función como prop y razona, mirando la frontera, por qué no sobrevive el viaje al cliente.