wandres.dev
POR QUÉ ASTRO · islands y content-first

La arquitectura de islas explicada

Hidratación parcial e independiente: cada componente interactivo es una isla con su propia raíz, frente al todo-o-nada de la hidratación monolítica de una SPA. El patrón que hace posible el cero-JS por defecto.

⏱ 13 min

“Islands architecture” es el nombre del patrón que sostiene todo lo demás. La idea, popularizada por Jason Miller en 2020, es simple y potente: renderiza la página como HTML estático y siembra en ella pequeñas regiones interactivas —islas— que se hidratan de forma independiente. Entender por qué esa independencia lo cambia todo es entender Astro.

🎯 Al terminar esta lección sabrás
  • Visualizar la página como un océano de HTML con islas interactivas.
  • Entender la hidratación parcial: hidratar partes, no la página entera.
  • Entender la hidratación independiente: cada isla es su propia raíz.
  • Contrastarla con la hidratación monolítica, todo-o-nada, de una SPA.

El océano de HTML y las islas

La metáfora es literal. La mayor parte de una página de contenido es un mar de HTML estático —cabeceras, párrafos, imágenes, enlaces— que no necesita JavaScript. Salpicadas en ese mar hay unas pocas regiones que sí lo necesitan: un buscador, un widget de comentarios, un selector de tema. Cada una de esas regiones es una isla: un componente interactivo, autónomo, rodeado de contenido inerte.

En una SPA no hay islas ni mar: todo es aplicación. La página es un único árbol de componentes con una sola raíz, y esa raíz gobierna hasta el último párrafo de texto. En Astro, la relación se invierte: el HTML es el continente y la interactividad son excepciones acotadas dentro de él. El patrón no lo inventó Astro —Katie Sierra Hanson lo describió antes y Jason Miller le puso nombre—, pero Astro fue el primero en construir un framework entero alrededor de la idea, en lugar de tratarla como una optimización opcional.

ℹ️
Isla no es lo mismo que componente

Un componente .astro no es una isla: se renderiza a HTML y desaparece. Una isla es específicamente un componente de framework —React, Svelte, Vue, Solid— al que le has puesto una directiva client:*. Solo entonces Astro emite JavaScript para él. La mayoría de tu página seguirá siendo componentes sin islas: HTML y nada más.

Hidratación parcial: solo lo que hace falta

La primera propiedad clave es la hidratación parcial. En lugar de hidratar el documento entero, Astro hidrata únicamente los nodos marcados como islas. Si tu página tiene un buscador y cincuenta párrafos, solo se envía y ejecuta el JavaScript del buscador. Los cincuenta párrafos jamás participan en la hidratación porque nunca fueron JavaScript: nacieron y murieron como HTML.

---
import Contador from '../components/Contador.jsx';
---
<h1>Un artículo largo</h1>
<p>Miles de palabras de HTML puro, sin coste de hidratación...</p>

<!-- La unica parte que envia y ejecuta JS es esta isla -->
<Contador client:load />

<p>...y sigue el HTML puro despues de la isla.</p>

La consecuencia es directa sobre las métricas del nivel 1.1: el TBT se desploma porque el hilo principal apenas tiene trabajo que hacer. No hay un árbol gigante que reconstruir, solo una isla diminuta que activar. Y como cada isla se empaqueta por separado, el JavaScript total de la página es la suma de sus islas, no un bundle monolítico: pagas exactamente por lo que declaras, ni un byte más.

El patrón produce cuatro efectos encadenados y todos medibles:

  • Menos bytes: solo viaja el JavaScript de las islas, no un framework para toda la página.
  • Menos bloqueo: el hilo principal hidrata unos pocos nodos, no un árbol entero.
  • Mejor TTI: la página responde en cuanto arranca la primera isla, no cuando termina la última.
  • Degradación elegante: si una isla falla, el resto del documento sigue en pie.

Hidratación independiente: cada isla, su propia raíz

La segunda propiedad, más sutil y más importante, es la independencia. Cada isla es una raíz de hidratación separada, con su propio fragmento de runtime y su propio ciclo de arranque. Las islas no comparten un árbol ni un scheduler común: se hidratan en paralelo y sin bloquearse entre sí.

Esto rompe el punto débil de la SPA. En una SPA la hidratación es todo-o-nada y de arriba abajo: hay una sola raíz, se reconstruye el árbol completo en una pasada, y si una sola parte es pesada, la interactividad de toda la página espera. Un widget lento contamina a todos los demás. En Astro, una isla pesada que tarda en hidratar no impide que una isla ligera de al lado responda de inmediato: son procesos aislados que arrancan cuando les toca.

flowchart TB
subgraph SPA[SPA: hidratacion todo o nada]
  R[Raiz unica] --> N1[Nav]
  R --> N2[Articulo]
  R --> N3[Comentarios]
  R --> N4[Footer]
end
subgraph ISLAS[Astro: islas independientes]
  H[Oceano de HTML estatico]
  H --- I1[Isla buscador]
  H --- I2[Isla comentarios]
end
style R fill:#f38ba8,color:#11111b
style H fill:#a6e3a1,color:#11111b
style I1 fill:#89b4fa,color:#11111b
style I2 fill:#89b4fa,color:#11111b

Ese aislamiento tiene un matiz que conviene saber pronto: como cada isla es su propia raíz, no comparten estado de forma automática. Los datos que pasas del servidor a una isla como props se serializan una vez, en el build o la petición, y quedan congelados dentro de esa isla. Si dos islas deben comunicarse en el cliente, se usa un almacén ligero compartido —por ejemplo, nanostores— o eventos del navegador, no el estado interno de un árbol común que aquí sencillamente no existe.

⚠️
Las islas no comparten contexto de cliente

Un error típico al venir de una SPA es esperar que un contexto global —un provider de tema, un store de React— se propague entre islas. No lo hace: cada isla es una aplicación aparte. La solución no es forzar una raíz común (eso reintroduce el problema de la SPA), sino elegir un mecanismo de estado pensado para atravesar islas, o subir esa lógica a un único componente que lo englobe.

Cuándo se hidrata: las estrategias de carga

La independencia también gobierna el cuándo. Cada isla decide su momento de hidratación con su directiva, y todas conviven en la misma página sin coordinarse. Elegir bien la directiva es la palanca principal de rendimiento que tendrás en tus manos.

client:load

Prioridad alta: hidrata de inmediato. Para lo que debe responder desde el primer segundo, como un botón de compra visible al entrar.

👁️

client:visible

Difiere hasta que la isla entra en pantalla, vía IntersectionObserver. No pagas por lo que nadie llega a ver.

💤

client:idle

Espera a que el hilo principal quede libre. Interactividad real pero no urgente, que puede llegar un instante después.

🧩

client:only

Omite el HTML del servidor y renderiza solo en el cliente. Para widgets que dependen por completo del navegador.

En la práctica, conviven en el mismo archivo, cada una con su estrategia declarada al lado del componente:

---
import Tema from '../components/Tema.jsx';
import Menu from '../components/Menu.svelte';
import Comentarios from '../components/Comentarios.jsx';
---
<Tema client:load />                          <!-- critico: hidrata ya -->
<Menu client:media="(max-width: 768px)" />    <!-- solo en movil -->
<Comentarios client:visible />                <!-- al llegar con el scroll -->

Hay una quinta, client:media, que hidrata solo si se cumple una media query: perfecta para un menú que únicamente existe en móvil, evitando cargar su JavaScript en escritorio. Y en 2026 el patrón se ha extendido al servidor con las server islands, que difieren la generación de un fragmento dinámico a una petición aparte, dejando el resto de la página cacheable como estático. La misma idea —aislar lo variable de lo estable— aplicada ahora en las dos direcciones.

📝
client:only omite el servidor

Algunas islas dependen de APIs que solo existen en el navegador —el objeto window, medidas del DOM, geolocalización—. Renderizarlas en el servidor produciría un desajuste al hidratar. Para ellas está client:only, que salta el HTML de servidor y renderiza directo en el cliente. Úsala con cuidado: esa isla no aportará nada al HTML inicial ni al SEO.

Qué merece de verdad ser isla

No todo lo interactivo necesita serlo. Una buena isla suele cumplir algo de esto:

  • Mantiene estado propio que cambia con el tiempo: un contador, un formulario, un filtro.
  • Reacciona a eventos más allá de un enlace: arrastrar, alternar, buscar mientras escribes.
  • Necesita el navegador para existir: un mapa, un canvas, un reproductor de vídeo.

Si algo se resuelve con un enlace, con un elemento nativo de HTML o con una animación de CSS, no es una isla: es contenido con estilo, y debe quedarse como HTML puro.

La independencia es el mecanismo, no un detalle

Es fácil quedarse con “Astro manda menos JavaScript” y perder lo esencial. Lo que hace único al patrón de islas no es la cantidad, sino la topología de la hidratación. Una SPA tiene una topología de árbol con una raíz: el rendimiento del conjunto está atado al eslabón más lento, porque todo cuelga de un mismo proceso de hidratación. La arquitectura de islas tiene una topología de archipiélago: nodos aislados sobre un fondo estático, cada uno con su propio arranque. Esa descomposición es lo que convierte el rendimiento en algo componible —añades una isla y solo pagas por esa isla— en lugar de acoplado —añades interactividad y encareces la página entera—. Por eso el cero-JS por defecto no es un truco de compilación: es la consecuencia natural de una arquitectura donde la interactividad se declara isla a isla, y todo lo que no se declara, sencillamente, nunca fue código.

⚔️ Descompón una página en islas
  1. Toma una página real con varias zonas interactivas: por ejemplo, un artículo con buscador, botón de “me gusta” y comentarios.
  2. Dibuja el árbol de hidratación que tendría como SPA: una sola raíz de la que cuelga todo.
  3. Ahora dibújala como archipiélago: el HTML de fondo y tres islas separadas.
  4. Asigna a cada isla su directiva y su justificación. ¿Cuál merece client:load y cuál puede esperar a client:visible?