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.
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.
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.
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.
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.
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.