El template: HTML con expresiones
La plantilla de un .astro como superconjunto de HTML: interpolar valores de la valla con expresiones entre llaves, renderizar listas y condicionales con JavaScript puro, componer atributos dinámicos, y por qué el resultado es HTML estático sin JavaScript de cliente.
Expresiones: interpolar la valla en el HTML
El puente entre la valla y la plantilla son las expresiones: cualquier fragmento de JavaScript encerrado entre llaves que Astro evalúa e inserta en su lugar. La forma más simple es interpolar una variable, pero dentro de las llaves cabe cualquier expresión —una propiedad, una llamada a método, una operación aritmética—, porque no es una mini-sintaxis, es JavaScript de verdad.
---const usuario = { nombre: "Ada", visitas: 42 };---<h1>Hola, {usuario.nombre}</h1><p>Llevas {usuario.visitas} visitas ({usuario.visitas * 2} puntos)</p>La restricción es sutil pero importante: las llaves aceptan expresiones, no sentencias. Puedes poner un ternario, un map o un &&, porque todos evalúan a un valor; no puedes poner un if, un for o un const, porque son sentencias que no producen un valor insertable. Toda la lógica que requiera sentencias vive arriba, en la valla; la plantilla solo interpola resultados.
---const precio = 1990;const moneda = "EUR";const enOferta = true;---<span>{(precio / 100).toFixed(2)} {moneda}</span>{enOferta && <span class="etiqueta">Oferta</span>}Dentro de las llaves cabe una operación aritmética, una llamada a un método como toFixed, un acceso a propiedad o un cortocircuito booleano, porque todo eso evalúa a un valor. Lo que no cabe es declarar, asignar o ramificar con if: esas son sentencias, y su sitio es la valla. La regla se resume en una frase: las llaves interpolan valores, no ejecutan programas.
Listas y condicionales con JavaScript
Astro no tiene un v-for ni un ngIf: usa las herramientas del propio lenguaje. Una lista se renderiza con .map, que transforma un array de datos en un array de elementos. Un condicional se expresa con && para mostrar-u-ocultar, o con un ternario para elegir entre dos ramas. Quien sabe JavaScript ya sabe la sintaxis de plantillas de Astro.
---const enlaces = [ { href: "/", texto: "Inicio" }, { href: "/blog", texto: "Blog" },];const sesionIniciada = true;---<nav> {enlaces.map((e) => <a href={e.href}>{e.texto}</a>)}</nav>{sesionIniciada ? <button>Salir</button> : <a href="/entrar">Entrar</a>}{enlaces.length === 0 && <p>No hay enlaces</p>}Este enfoque tiene una virtud pedagógica enorme: no hay una capa de abstracción propietaria que aprender y desaprender. El día que cambies de framework, tu conocimiento de map, ternarios y cortocircuitos booleanos viaja contigo, porque nunca fue conocimiento de Astro, sino de JavaScript.
Cuando el elemento repetido no debe envolverse en un contenedor, usas un Fragment —integrado en Astro, sin import— para agrupar varios nodos sin emitir uno extra. Astro no exige la prop key que pide React, pero conviene dar una clave estable si más tarde hidratas esa lista con un framework:
---const filas = [ { id: 1, nombre: "Ada" }, { id: 2, nombre: "Alan" },];---<dl> {filas.map((f) => ( <Fragment> <dt>{f.id}</dt> <dd>{f.nombre}</dd> </Fragment> ))}</dl>Fíjate en que el cuerpo del map devuelve marcado directamente: en la plantilla de Astro, el HTML y las expresiones se funden, y una función flecha puede retornar elementos sin ceremonia. Es el mismo patrón mental que en React, salvo que aquí su resultado se congela en el build en vez de vivir en el navegador.
Atributos dinámicos y casos especiales
Los atributos también aceptan expresiones: <a href={url}> inserta el valor de url. Sobre esta base, Astro añade unas pocas comodidades para los casos que en HTML plano resultan tediosos, sin apartarse de su filosofía de no reinventar demasiado.
---const activo = true;const contenidoConfiable = "<strong>Ya renderizado</strong>";---<button class:list={["btn", { activo }]}>Enviar</button><article set:html={contenidoConfiable} />Por defecto Astro escapa todas las expresiones: si una variable contiene <script>, se imprime como texto inofensivo, no como etiqueta ejecutable. Esa es tu red de seguridad frente a la inyección. set:html es la puerta que la desactiva a propósito, y por eso solo debe recibir contenido que tú controlas —Markdown ya procesado, HTML sanitizado—, nunca texto crudo de un usuario.
Por qué el resultado es HTML sin JS
Aquí está el corazón de la lección. Cuando Astro procesa la plantilla, evalúa cada expresión en el momento del build o de la petición y la sustituye por su valor. El map se ejecuta y produce las etiquetas; el ternario se resuelve y elige una rama; la variable se reemplaza por su texto. Lo que queda es HTML plano, sin rastro de las llaves ni del JavaScript que las llenó.
Esta es la razón mecánica de que un .astro no envíe JavaScript: sus expresiones no son código que corra en el navegador, sino código que corrió antes para producir el marcado. El visitante recibe el HTML ya resuelto, sin la lógica que lo generó, del mismo modo que recibes un plato cocinado sin la cocina. Ningún runtime acompaña a la página porque no hace falta ninguno: no hay nada que reejecutar.
Merece subrayar el contraste con el renderizado del lado del cliente. Allí el servidor envía datos y una plantilla, y es el navegador quien, ejecutando JavaScript, combina ambos para producir el DOM; la primera pintura depende de que ese código descargue, se analice y corra. En un .astro ese trabajo ya se hizo antes de tiempo: el navegador recibe el DOM terminado y solo tiene que dibujarlo, sin un paso previo de cómputo.