.astro frente a componentes de framework
Cuándo usar un componente .astro y cuándo uno de framework como React o Solid: el contraste entre renderizar HTML en el build y ofrecer interactividad en el cliente, por qué los .astro no son reactivos por diseño, y cómo las directivas client convierten un componente en una isla hidratada.
Astro te deja escribir componentes en su propio formato .astro y también en React, Solid, Vue, Svelte o Preact, todos en el mismo proyecto. No es indecisión: es una división del trabajo deliberada. El .astro produce HTML y desaparece; el componente de framework puede cobrar vida en el navegador. Saber a cuál acudir en cada caso es la destreza que separa un sitio ligero de uno que arrastra JavaScript sin necesidad.
- Contrastar el modelo del
.astro—HTML en el build— con el de un componente de framework. - Entender que los
.astrono son reactivos en el cliente por diseño, no por carencia. - Decidir cuándo basta un
.astroy cuándo necesitas una isla interactiva de framework. - Situar las directivas
client:*como el puente que hidrata un componente concreto.
Dos clases de componente
Un componente .astro y uno de React resuelven problemas distintos, aunque ambos se llamen componente. El .astro es una plantilla de servidor: se ejecuta en el build o en la petición, emite HTML y no lleva ningún runtime al cliente. Un componente de framework describe una interfaz que puede ejecutarse en el navegador, con estado, eventos y repintado —y para hacerlo necesita que su librería viaje al cliente—.
Por defecto, y esto sorprende a muchos, Astro renderiza también los componentes de framework a HTML estático en el servidor. Un <Contador /> de React colocado sin más en una página produce su marcado inicial y nada de JavaScript: se comporta como un .astro. La interactividad no viene de escribir React, sino de pedirla explícitamente con una directiva.
---
import Precio from '../components/Precio.astro'; // plantilla de servidor
import Cesta from '../components/Cesta.tsx'; // componente de React
---
<Precio valor={39} />
<Cesta /> <!-- se renderiza a HTML, aun sin interactividad -->
En esta página conviven las dos clases sin fricción. Precio solo puede ser HTML: no existe otra vía. Cesta, en cambio, es HTML por ahora, pero guarda un potencial que Precio nunca tendrá: si se lo pides, correrá en el navegador. Escribir el componente en React no activa ese potencial; escribir la directiva, sí.
Componente .astro
Plantilla de servidor. Se resuelve a HTML en el build. Nunca envía JavaScript de cliente. No tiene estado ni eventos.
Componente de framework
React, Solid, Vue, Svelte. Renderiza a HTML por defecto y, si lo hidratas, corre en el navegador con estado y eventos.
El matiz decisivo es que la frontera no está en el lenguaje sino en el destino de ejecución. Un componente de framework sin hidratar y un .astro producen, para el visitante, exactamente lo mismo: HTML inerte. Lo que los separa no es cómo los escribiste, sino si alguna vez llegan a ejecutarse en el navegador. Escribir en React es una condición necesaria para la interactividad, pero nunca suficiente por sí sola.
No reactivo por diseño
Un .astro no reacciona en el cliente, y conviene decirlo con precisión: no es que Astro no haya sabido hacerlo reactivo, es que su plantilla no describe una relación viva entre estado y marcado. Como viste, sus expresiones se evalúan una vez y quedan impresas. No hay useState, no hay repintado, no hay listeners que sobrevivan al renderizado, porque el modelo entero es una proyección de datos a HTML que ocurre y termina antes de que exista el navegador.
Esto es una elección de diseño, no una limitación. La inmensa mayoría del contenido de una web no cambia tras cargarse —textos, cabeceras, listados, artículos— y para todo eso la reactividad es peso muerto: un motor que vigila cambios que nunca llegan. Astro parte de la posición contraria a la de los frameworks de cliente: nada es interactivo salvo que tú lo pidas, en lugar de que todo lo sea aunque no lo necesites.
---
// intento ingenuo de un contador dentro de un .astro
let contador = 0;
function sumar() { contador++; }
---
<button>Suma</button>
<p>{contador}</p>
Este código compila, pero el botón nunca cambiará el número. La función sumar no se conecta a ningún evento del navegador, y contador se evaluó una sola vez en el build valiendo cero, dejando el p impreso con ese cero para siempre. No hay motor de cliente que observe la variable ni repinte el elemento tras un clic. Para que ese contador cobre vida, su lógica debe mudarse a un componente de framework hidratado: la valla no es el lugar de la interactividad.
Si te sorprendes intentando cambiar una variable de la valla desde un clic, o esperando que la plantilla se repinte al modificar un dato, estás usando la herramienta equivocada para ese fragmento. Un .astro no tiene ciclo de vida de cliente. Eso que quieres —estado que cambia, UI que responde— es justo la frontera donde debe entrar un componente de framework. No fuerces la valla a hacer de cliente: cambia de herramienta para esa pieza concreta.
Cuándo cada uno
La regla práctica es económica: usa .astro por defecto y reserva un componente de framework para las islas que de verdad necesitan comportarse en el navegador. El coste de un .astro para el cliente es cero; el de una isla hidratada es el JavaScript de su framework más su propio código. Cada isla se justifica por sí misma.
flowchart TD
Q{necesita interactividad en el cliente} -->|no| ASTRO[componente .astro]
Q -->|si| FW[componente de framework]
ASTRO --> ZERO[cero JS enviado al cliente]
FW --> DIR{lleva directiva client}
DIR -->|si| HYDRATE[isla hidratada en el navegador]
DIR -->|no| STATIC[render estatico a HTML sin JS]
style Q fill:#89b4fa,color:#11111b
style ZERO fill:#a6e3a1,color:#11111bEn la práctica, la cabecera, el pie, la maquetación, las tarjetas, los artículos y el noventa por ciento del sitio son .astro. El carrusel con arrastre, el buscador con autocompletado, el carrito que actualiza un total, el formulario con validación en vivo son componentes de framework. La frontera no la marca la complejidad visual, sino si la pieza necesita responder al usuario después de cargarse.
Trabajo de .astro
Cabeceras, pies, artículos, tarjetas, navegación, tablas, listados. Marcado que se muestra y no cambia.
Trabajo de isla
Buscadores con autocompletado, carritos, carruseles, formularios validados en vivo, gráficos interactivos.
Una heurística fiable para empezar: escríbelo todo en .astro y espera a sentir la falta de interactividad. Cuando un elemento pida de verdad responder a un clic, un tecleo o un gesto, extrae solo esa pieza a un componente de framework. Así el JavaScript de tu sitio crece por necesidad demostrada, nunca por hábito heredado.
Partir de .astro y añadir islas puntuales invierte el valor por defecto de los frameworks tradicionales, donde todo es interactivo y tú recortas. Aquí nada lo es y tú añades. El resultado práctico es que el caso común —contenido que no cambia— sale gratis, y solo pagas JavaScript donde tomaste la decisión explícita de necesitarlo. Es la diferencia entre optar por entrar y optar por salir, aplicada al peso de tu página.
Islas: el punto de encuentro
El puente entre ambos mundos son las directivas client:*. Colocas un componente de framework en una plantilla .astro y le añades una directiva que le dice a Astro cuándo hidratarlo —enviar su JavaScript y activarlo—. Sin directiva, ese componente es HTML estático; con ella, se convierte en una isla: una región interactiva rodeada de HTML inerte.
---
import Cabecera from '../components/Cabecera.astro'; // .astro estatico
import Buscador from '../components/Buscador.jsx'; // isla de framework
---
<Cabecera titulo="Panel" />
<Buscador client:load />
<footer>Contenido estático, cero JavaScript</footer>
Las directivas gradúan cuándo ocurre la hidratación: client:load activa la isla en cuanto carga la página; client:idle espera a que el navegador esté ocioso; client:visible la difiere hasta que la isla entra en pantalla. Esa granularidad te deja pagar el coste de JavaScript solo cuando y donde aporta valor, en lugar de hidratar la página entera de golpe.
---
import Menu from '../components/Menu.astro';
import Carrusel from '../components/Carrusel.svelte';
import Chat from '../components/Chat.tsx';
---
<Menu />
<Carrusel client:visible />
<Chat client:idle />
Cada isla elige su framework y su momento de hidratación de forma independiente: aquí un carrusel de Svelte que despierta al entrar en pantalla convive con un chat de React que espera a que el navegador quede ocioso, todo alrededor de un menú .astro que jamás envía JavaScript. Ese es el modelo de islas condensado en una sola pantalla. La isla recibe props desde su anfitrión .astro como cualquier componente, pero con una salvedad: esos valores se serializan para cruzar del servidor al cliente, así que deben ser datos simples —cadenas, números, objetos planos—, nunca funciones ni instancias complejas.
Algunas islas dependen de APIs que solo viven en el navegador —window, localStorage— y fallan si Astro intenta renderizarlas en el servidor. Para ellas está client:only, que omite el render de servidor y monta el componente directamente en el cliente. Es la excepción, no la regla: úsala solo cuando el render de servidor sea imposible, porque renuncias al HTML inicial y con él a parte de la ventaja de Astro.
En conjunto, estas directivas convierten la vieja pregunta binaria —sitio estático o aplicación— en un dial continuo. Cada componente elige su punto: cero JavaScript, hidratación diferida, hidratación inmediata o montaje solo en el cliente. Astro no te obliga a una postura global; te entrega un mando por isla y te pide usarlo con criterio.
La mejor guía para decidir entre .astro e isla no es la teoría, sino la pestaña de red del navegador: construye, abre las herramientas de desarrollo y observa cuánto JavaScript descarga cada página. Cuando veas el peso real de una isla, la pregunta de si de verdad necesita ser interactiva deja de ser abstracta y se convierte en un número que puedes defender.
El debate habitual entre tecnologías de front se plantea como una elección de bando: React o Vue, Solid o Svelte, como si adoptar uno definiera tu arquitectura. Astro reencuadra la pregunta y la vuelve más honesta: no qué framework, sino dónde debe vivir cada cómputo. Para cada pieza de tu interfaz hay una decisión previa a la del framework —¿este trabajo pertenece al servidor, que lo hace una vez y lo olvida, o al cliente, que debe rehacerlo ante cada interacción?—. Los frameworks tradicionales contestan esa pregunta por ti y siempre igual: todo al cliente, porque su unidad de composición es un componente que corre en el navegador. Astro te devuelve la decisión, elemento por elemento. Un .astro es la afirmación de que un cómputo pertenece al servidor: se resuelve en el build y su resultado, ya frío, se sirve sin runtime. Una isla es la afirmación contraria y acotada: este fragmento, y solo este, necesita seguir pensando en el navegador, así que le concedemos su framework y su JavaScript, encapsulados en una región del tamaño justo. La consecuencia es que dejas de razonar en términos de una aplicación monolítica que se hidrata entera y empiezas a razonar en términos de un documento mayormente estático salpicado de interactividad puntual —que es, si lo miras sin prejuicios, lo que casi todas las webs son en realidad—. Y como cada framework se carga solo dentro de su isla, la elección entre React y Solid se vuelve local, reversible y hasta plural: puedes tener una isla de React junto a una de Svelte en la misma página, porque ninguna reclama ser el runtime de todo el sitio. La madurez arquitectónica que Astro pide no es dominar un framework, sino cultivar el juicio de preguntarte, ante cada componente, si de verdad necesita estar vivo en el cliente. Casi nunca lo necesita. Y cuando lo necesita, lo sabrás con una claridad que el modelo de hidratar-todo jamás te concede.
- Escribe un
Contador.jsxcon un botón que incremente un estado; colócalo en una página.astrosin directiva y comprueba en el HTML que se renderiza pero no reacciona al clic. - Añade
client:loadal contador, recarga y verifica que ahora funciona; inspecciona la red y observa el JavaScript que se envió para esa isla. - Cambia la directiva a
client:visible, sitúa el contador al final de una página larga y confirma que su JavaScript solo se carga al hacer scroll hasta él. - Recorre una página propia y clasifica cada pieza en
.astroo isla de framework, justificando en cada caso si necesita responder al usuario después de cargarse.