Cómo los componentes Astro generan HTML puro sin JavaScript en el cliente

DUGLAS MORENODUGLAS MORENO 👁 16

Astro es un framework moderno pensado para construir sitios web rápidos enfocándose en el contenido. Una de sus características más distintivas es que, por defecto, los componentes .astro se renderizan a HTML estático y no envían JavaScript al navegador, a menos que el desarrollador lo solicite explícitamente mediante directivas de cliente. En este artículo vamos a desglosar cómo funciona ese proceso, qué ocurre bajo el capó y cómo podés aprovecharlo para crear páginas ultra‑ligeras. También incluiremos ejemplos concretos de componentes Astro y la salida HTML que producirían, así que podés ver directamente el resultado sin necesidad de ejecutar nada.

¿Por qué es importante el renderizado sin JS?

En la actualidad la mayor parte del peso de una página proviene de los bundles de JavaScript que el navegador debe descargar, analizar y ejecutar antes de poder mostrar contenido útil. Cada kilobyte extra de JS incrementa el tiempo de carga, afecta la experiencia en conexiones lentas y consume batería en dispositivos móviles. Astro aborda este problema desde la raíz: trata el HTML como el producto principal y deja que JavaScript sea un opcional que se agrega solo cuando realmente se necesita interactividad.

Este enfoque beneficia a:

  • Sitios de contenido: blogs, documentación, landing pages.
  • Proyectos donde el SEO es crítico, porque los crawlers reciben HTML completo sin necesidad de ejecutar scripts.
  • Equipos que quieren mantener bajo control el bundle sin sacrificar la posibilidad de usar componentes de React, Svelte o Vue cuando lo.

Arquitectura básica de un componente Astro

Un archivo .astro se compone de tres partes opcionales:

  1. Frontmatter (entre ---): código JavaScript o TypeScript que se ejecuta en el servidor durante el build. Aquí podés definir props, hacer consultas a datos, importar otros componentes, etc.
  2. Template HTML: la estructura que se enviará al cliente. Puede contener expresiones de JavaScript entre {} que se evalúan en el servidor y se reemplazan por su resultado.
  3. Estilos scoped (opcional): bloques <style> que Astro transforma en CSS con alcance limitado al componente.

Cuando Astro procesa el componente, el frontmatter se ejecuta en Node (o en el entorno de build) y el template se renderiza a una cadena de HTML puro. Ese HTML es lo que se escribe en el archivo de salida (por ejemplo, dist/index.html). No se incluye ninguna etiqueta <script> generada por Astro a menos que se usen directivas de cliente como client:load, client:idle o client:visible.

Ejemplo 1: componente estático sencillo

Creemos un componente llamado Header.astro que simplemente muestra un título y un enlace.

---
// Frontmatter vacío, nada que ejecutar en el servidor
---
<header>
  <h1>Mi sitio con Astro</h1>
  <nav>
    <a href=\ /\">Inicio</a> |
    <a href=\ /about\">Acerca de</a>
  </nav>
</header>

<style>
  header { background: #f8f8f8; padding: 1rem; }
  h1 { margin: 0; font-size: 1.8rem; }
  nav a { margin: 0 .5rem; text-decoration: none; color: #0066cc; }
</style>

Al construir el proyecto, Astro producirá el siguiente HTML (los estilos se extraen a un archivo CSS separado, pero aquí los mostramos embebidos para claridad):

<header>
  <h1>Mi sitio con Astro</h1>
  <nav>
    <a href=\"\//
">Inicio</a> |
    <a href=\"\/about\"
">Acerca de</a>
  </nav>
</header>

Observá que no hay ninguna etiqueta <script> ni atributos especiales como data-astro-cid. El componente es puro HTML; los estilos se manejan mediante un CSS externo generado por Astro, pero eso no afecta la carga de JavaScript.

Ejemplo 2: componente que recibe props

Los componentes Astro pueden aceptar props definidas en el frontmatter. Estas props se pasan desde el componente padre y se usan en el template como variables locales.

---
export interface Props {
  title: string;
  subtitle?: string;
}
const { title, subtitle } = Astro.props;
---
<section>
  <h2>{title}</h2>
  {subtitle && <p class=\"subtitle\"> {subtitle} </p>}
  <p>Este contenido se renderiza en el servidor.</p>
</section>

<style>
  .subtitle { color: #666; font-size: .9rem; }
</style>

Si lo usamos así desde una página:

---
import Header from \"../components/Header.astro\
\); import Intro from \"../components/Intro.astro\
---
<Header />
<Intro title=\"Bienvenidos\" subtitle=\"Aprendiendo Astro\" />

El HTML resultante (ignorando el <Header> por brevedad) será:

<section>
  <h2>Bienvenidos</h2>
  <p class=\"subtitle\">Aprendiendo Astro</p>
  <p>Este contenido se renderiza en el servidor.</p>
</section>

Nuevamente, cero JavaScript enviado al cliente. Las expresiones {title} y {subtitle && <p>…</p>} fueron evaluadas durante el build y sustituidas por su valor o eliminadas si la condición era falsa.

Ejemplo 3: listas y loops en el template

Astro permite usar la sintaxis de JavaScript dentro del template para iterar sobre arreglos o objetos. Como todo se evalúa en el servidor, el bucle se resuelve antes de enviar el HTML.

---
const items = [
  { name: \"React\", url: \"https://reactjs.org\" },
  { name: \"Vue\", url: \"https://vuejs.org\" },
  { name: \"Svelte\", url: \"https://svelte.dev\" },
];
---
<ul>
  {items.map(item => (
    <li>
      <a href={item.url}>{item.name}</a>
    </li>
  ))}
</ul>

<style>
  li { margin-bottom: .4rem; }
  a { color: #0066cc; }
</style>

Salida esperada:

<ul>
  <li><a href=\"https://reactjs.org\">React</a></li>
  <li><a href=\"https://vuejs.org\">Vue</a></li>
  <li><a href=\"https://svelte.dev\">Svelte</a></li>
</ul>

El mapa se ejecutó en Node, generando tres <li> con sus enlaces. No se incluyó ningún código de iteración en el cliente.

Ejemplo 4: uso de slots (ranuras)

Los componentes Astro pueden aceptar contenido hijo mediante <slot/>. Esto permite crear componentes reutilizables como tarjetas, modales o layouts sin enviar JavaScript extra.

---
// Card.astro
---
<div class=\"card\">
  <div class=\"card-header\">
    <slot name=\"header\">
      <strong>Título predeterminado</strong>
    </slot>
  </div>
  <div class=\"card-body\">
    <slot />
  </div>
</div>

<style>
  .card { border: 1px solid #ddd; border-radius: 8px; padding: 1rem; }
  .card-header { font-weight: bold; margin-bottom: .5rem; }
</style>

Uso desde una página:

---
import Card from \"../components/Card.astro\
\);\
---
<Card>
  <span slot=\"header\">Encabezado personalizado</span>
  <p>Este es el cuerpo de la tarjeta. Puede contener cualquier HTML.</p>
</Card>

Resultado HTML:

<div class=\"card\">
  <div class=\"card-header\">
    Encabezado personalizado
  </div>
  <div class=\"card-body\">
    <p>Este es el cuerpo de la tarjeta. Puede contener cualquier HTML.</p>
  </div>
</div>

El slot se remplazo durante el renderizado del servidor; no hubo necesidad de que el cliente gestionara nada dinámicamente.

¿Cómo evita Astro enviar JavaScript por defecto?

Durante el proceso de build, Astro realiza los siguientes pasos:

  1. Extrae el frontmatter y lo ejecuta en un entorno de Node (o en el edge runtime si se usa adaptador).
  2. Renderiza el template a una cadena de HTML, sustituyendo todas las expresiones {} y resolviendo condicionales, loops y llamadas a componentes.
  3. Separa los estilos (<style>) en archivos CSS únicos o los inyecta mediante <link>/<style> según la configuración.
  4. Genera el archivo final (por ejemplo, dist/index.html) que contiene únicamente el HTML resultante y referencias a los assets CSS.
  5. Omite cualquier etiqueta <script> que no haya sido solicitada explícitamente mediante una directiva de cliente.

Porque Astro no necesita un "runtime" en el navegador para interpolar datos o manejar estado: todo eso se resuelve antes de que el HTML llegue al cliente. El único JavaScript que podría aparecer es el que el desarrollador decida cargar (por ejemplo, un componente de React hidratado) o los scripts que se importen manualmente en el frontmatter y se marquen con client:.*

Directivas de cliente: cuándo sí se envía JS

Astro ofrece cuatro directivas principales para controlar la hidratación:

  • client:load: el componente se hidrata inmediatamente después de que la página se carga.
  • client:idle: se hidrata cuando el hilo principal está libre (usando requestIdleCallback).
  • client:visible: se hidrata cuando el componente entra en el viewport (IntersectionObserver).
  • client:only: se omite el renderizado en el servidor y se envía únicamente el componente hidratado (útil para widgets que dependen totalmente del cliente).

Ejemplo de un componente interactivo que se hidrata al ser visible:

---
// Counter.astro
---
<script>
  // Este código solo se ejecuta en el cliente después de la hidratación
  let count = 0;
  function increment() {
    count++;
    document.getElementById(\"count\\)).textContent = count;
  }
</script>

<div>
  <p>Has hecho clic <strong id=\"count\">0</strong> veces.</p>
  <button onclick=\"increment()\">+1</button>
</div>

<!-- Directiva: hidratar cuando el componente sea visible -->
<Counter client:visible />

En el build, Astro generará el HTML del componente (el <div> con el párrafo y el botón) pero añadirá un pequeño script que carga la lógica de forma asincrónica el bundle de JavaScript necesario para hidratar el componente cuando aparezca en pantalla. Hasta ese momento, el cliente recibe solo HTML.

Buenas prácticas para mantener el HTML puro

  1. Preferí componentes .astro para todo lo estático: encabezados, pies de página, listas, tarjetas, bloques de contenido.
  2. Usá frontmatter solo para lógica de servidor: consultas a APIs, lectura de archivos Markdown, generación de metadatos SEO.
  3. Limité el uso de directivas de cliente: cargar JavaScript solo cuando realmente necesites interactividad (formularios, carruseles, modales, etc.).
  4. Aprovechá los componentes de UI frameworks (React, Preact, Svelte, Vue) únicamente dentro de directivas de cliente; de lo contrario, Astro los tratará como componentes estáticos y eliminará su JS.
  5. Optimizá los estilos: Astro genera CSS con scope automático; evité sobrescribir estilos globales innecesariamente.
  6. Hacé pruebas de rendimiento con Lighthouse o WebPageTest para confirmar que el score de "JavaScript execution time" se mantiene bajo.

Errores comunes y cómo evitarlos

  • Poner lógica de cliente dentro del frontmatter: el frontmatter corre en el servidor; si intentás acceder a window o document ahí, obtendrás un error de build. Mover ese código a un <script> con directiva de cliente.
  • Olvidarse de exportar props: si definís una interfaz pero no destructurás Astro.props, las props quedaran undefined y el template mostrará valores vacíos.
  • Usar expresiones complejas que dependan del estado del cliente (por ejemplo, { Math.random() }). Eso se evaluará una sola vez en el servidor y producirá el mismo valor en cada request, lo cual suele no ser lo deseado.
  • Crear componentes gigantes con mucho HTML y sin dividirlos: aunque el HTML se envía sin JS, un archivo muy grande afecta el tiempo de transferencia; dividir en componentes parciales ayuda a reutilizar y a mantener el tamaño bajo.
  • Confundir client:only con client:load: el primero omite el SSR y puede afectar el SEO si el contenido es crítico para los crawlers.

Conclusión

Astro cambia el paradigma del desarrollo web al hacer que el HTML sea el artefacto primario y el JavaScript un recurso opcional que se agrega bajo demanda. Al comprender cómo funciona el renderizado por defecto — frontmatter ejecutado en el servidor, template convertido a cadena de HTML puro, estilos separados y ausencia de scripts automáticos — podés diseñar aplicaciones que carguen casi instantáneamente, obtengan mejor puntaje en Core Web Vitals y sean amigables con los motores de búsqueda.

Los ejemplos mostrados demuestran que, desde un simple header hasta componentes con props, loops, slots y estilos scoped, todo se puede lograr sin escribir una sola línea de JavaScript que llegue al navegador. Cuando realmente necesites interactividad, las directivas de cliente te brindan un control granular sobre cuándo y cómo se hidrata cada parte de la interfaz.

Así que, la próxima vez que estés estructurando un sitio, pensá primero en qué contenido se puede entregar como HTML estático y reservá el JavaScript solo para las piezas que realmente deben vivir en el cliente. Con Astro, esa separación no solo es posible, sino que es la forma natural de trabajar.


¿Te quedó alguna duda sobre cómo aprovechar al máximo el renderizado sin JS de Astro? Dejála en los comentarios y seguimos la conversación.

Comentarios (0)

Sé el primero en comentar.

Dejá tu comentario