Saltar al contenido

Expresiones en el template

La llave como interpolador de expresiones JavaScript, la frontera entre el frontmatter y el HTML, y la diferencia esencial con JSX: en Astro las expresiones se evaluan una sola vez en el servidor y su resultado se congela en el marcado.

La llave: del frontmatter a la plantilla

Un .astro es literalmente dos zonas. Entre las dos líneas de --- vive el frontmatter: JavaScript o TypeScript normal que se ejecuta en el servidor antes de emitir nada. Debajo empieza la plantilla, que es HTML con una única superpotencia: la llave. Todo lo que escribes fuera de una llave es texto literal; todo lo que encierras en una llave es una expresión que Astro evalúa y sustituye por su valor en ese hueco exacto.

ASTRO
---const nombre = 'Ada';const anio = 2026;const saludar = (quien) => `Hola, ${quien}`;---<h1>{saludar(nombre)}</h1><p>Año actual: {anio}. El próximo: {anio + 1}.</p>

Dentro de la llave no hay una mini-sintaxis que memorizar: es JavaScript de verdad. Interpolas una variable, sí, pero también una llamada a función, una operación aritmética, un acceso a propiedad o el resultado de encadenar métodos. La plantilla no aprende un lenguaje nuevo; hereda el que ya conoces. Esa es la primera decisión de diseño que conviene interiorizar: Astro no reinventa el control de flujo, lo toma prestado del lenguaje anfitrión.

Fuera de las llaves, todo es texto literal, incluida la separación entre elementos: los espacios, los saltos de línea y la puntuación que escribes se emiten tal cual. Dentro de una llave puede ir una expresión tan escueta como un nombre o tan rica como una cadena de métodos con encadenamiento opcional; Astro no pone límite a su complejidad, solo a su categoría gramatical.

ASTRO
---const autor = { nombre: 'Ada', obras: ['Notas', 'Análisis'] };---<p>{autor.nombre} escribió {autor.obras.length} obras.</p><p>La primera, en mayúsculas: {autor.obras.at(0)?.toUpperCase()}</p>

Expresión, no sentencia

La restricción central es sutil y define todo lo demás: la llave admite una expresión, no una sentencia. Una expresión evalúa a un valor —2 + 2, usuario.nombre, saludar(nombre)—; una sentencia realiza una acción y no produce valor alguno —if, for, while, const, return—. Como Astro necesita algo que insertar en el hueco, solo lo que tiene valor cabe ahí.

El compilador te lo recuerda sin piedad: colar una sentencia en la llave es un error de sintaxis, no un aviso suave que puedas ignorar.

ASTRO
{/* válido: expresiones, todas devuelven un valor */}<p>{esVip ? 'Bienvenido' : 'Hola'}</p><ul>{tareas.map((t) => <li>{t}</li>)}</ul>{/* inválido: sentencias, no caben en la llave */}{if (esVip) { ... }}{for (const t of tareas) { ... }}

¿Cómo ramificas o iteras entonces en la plantilla? Traduciendo cada sentencia a su forma de expresión: el ternario sustituye al if, el método .map sustituye al for, el operador && expresa el mostrar-u-ocultar. Y cuando la lógica crece hasta volverse ilegible, no la fuerzas dentro de la llave: la subes al frontmatter, le das un nombre claro y dejas en la plantilla solo la interpolación del resultado. La regla práctica es una línea: calcula arriba, interpola abajo.

Evaluación en el servidor: la frontera con JSX

Aquí está el corazón de la lección, y el punto donde la intuición de quien viene de React falla. En un framework de cliente, la llave de JSX describe un vínculo vivo: la función del componente se reejecuta en cada cambio de estado y la expresión se reevalúa una y otra vez en el navegador. En Astro no hay tal cosa. El componente .astro corre exactamente una vez —en el build para un sitio estático, en la petición para SSR— y nunca más. No hay re-render, ni estado reactivo, ni hooks. La llave se evalúa una sola vez y su resultado se serializa a una cadena de HTML.

ASTRO
---const hora = new Date().toLocaleTimeString();---<p>Página renderizada a las {hora}</p><!-- salida: <p>Página renderizada a las 12:00:00</p> -->

En el HTML que llega al navegador no queda new Date() ni la llave: solo el texto ya calculado. El navegador jamás ejecuta esa expresión; recibe su ceniza. Por eso un onclick={...} no cablea un manejador vivo como en React: Astro es, en esencia, un motor de plantillas con JavaScript real, más cercano a un render de servidor que a un runtime de cliente. La interactividad del navegador entra por otra puerta —las islas y las directivas client:*— que verás más adelante, y que asumes conscientemente cuando la necesitas.

Un par de detalles cierran el cuadro. Todo valor interpolado se escapa como HTML por defecto: si una variable contiene <script>, se pinta como texto inerte, no como etiqueta ejecutable; es la defensa de Astro contra la inyección, y para inyectar HTML crudo hay que pedirlo con set:html. Y algunos valores se comportan de forma peculiar al interpolarse: false, null y undefined no pintan nada —utilísimo para condicionales—, pero el número 0 sí pinta su “0”, una trampa que reaparecerá en la próxima lección.

Conviene precisar cuándo es ese “una vez”. En un sitio estático, la expresión se evalúa en el build, y su resultado queda idéntico para todos los visitantes hasta la próxima reconstrucción. En una ruta renderizada bajo demanda (SSR), se evalúa en cada petición, así que puede variar entre un visitante y otro según la cookie, la hora o la base de datos. En ambos casos la mecánica es la misma —se evalúa en el servidor y se congela—; lo único que cambia es cada cuánto vuelve a ejecutarse ese servidor. La llave no sabe ni le importa en cuál de los dos modos corre: siempre entrega una fotografía, solo que a veces se retoma en cada visita.

El escapado automático como red de seguridad

Merece detenerse en el escapado, porque es una de las defensas silenciosas de Astro. Cuando interpolas una cadena, Astro convierte los caracteres especiales de HTML —los que abren etiquetas o entidades— en sus versiones inertes antes de escribirlos. Una variable que contenga marcado con un manejador malicioso no ejecuta nada: se muestra como texto visible, carácter por carácter.

ASTRO
---const entrada = '<img src=x onerror="robar()">';---<p>{entrada}</p><!-- salida: <p>&lt;img src=x onerror="robar()"&gt;</p> -->

Esta red está siempre puesta y no cuesta un rendimiento perceptible. Solo se retira cuando tú lo pides explícitamente, con la directiva set:html, que estudiarás al final del nivel. Hasta entonces la regla es tranquilizadora: cualquier dato que interpoles entre llaves llega al navegador como texto, nunca como código ejecutable. La inyección de HTML en Astro no es un descuido posible por defecto, sino una decisión que hay que escribir con todas las letras.