Astro a fondo 3, imágenes, islas y transiciones

Un portafolio de diseño son, sobre todo, imágenes.

Las imágenes de src/ se importan y se pintan con <Image /> o <Picture />: WebP o AVIF, tamaño justo y sin saltos.

Un <script> en un componente es JavaScript para el navegador y se incluye una sola vez.

Las directivas client: convierten un componente de React, Vue o Svelte en una isla interactiva.

<ClientRouter /> y transition:name animan el cambio de página; astro:page-load repite los scripts.

  • Un portafolio de diseño son, sobre todo, imágenes. Astro las convierte a WebP, les pone el tamaño y la carga diferida, y te deja añadir interacción solo donde hace falta. Esta página parte del portafolio de las dos anteriores.
  • DóndeQué hace AstroPara src/assets/ (o cualquier carpeta de src/)La optimiza: formato, tamaño, width y heightFotos y portadas de proyectos public/Nada: la copia tal cualFavicon, imágenes para redes, ficheros que no se tocan
  • Las de src/ se importan y se pintan con el componente <Image />:
  • --- import { Image } from 'astro:assets'; import retrato from '../assets/retrato-lucia.jpg'; --- <Image src={retrato} alt="Lucía Prieto en su estudio, delante de un tablero de bocetos" width={480} />
  • El HTML que genera:

Introducción

Un portafolio de diseño son, sobre todo, imágenes. Astro las convierte a WebP, les pone el tamaño y la carga diferida, y te deja añadir interacción solo donde hace falta. Esta página parte del portafolio de las dos anteriores.

Imágenes: src/ o public/

DóndeQué hace AstroPara src/assets/ (o cualquier carpeta de src/)La optimiza: formato, tamaño, width y heightFotos y portadas de proyectos public/Nada: la copia tal cualFavicon, imágenes para redes, ficheros que no se tocan

Las de src/ se importan y se pintan con el componente <Image />:

--- import { Image } from 'astro:assets'; import retrato from '../assets/retrato-lucia.jpg'; --- <Image src={retrato} alt="Lucía Prieto en su estudio, delante de un tablero de bocetos" width={480} />

El HTML que genera:

JavaScript en el navegador: <script>

Lo que va entre --- se ejecuta al construir. Para que algo pase en el navegador (un botón, un menú que se abre), un <script> en el componente:

--- // src/components/BotonTema.astro --- <button type="button" class="boton-tema" aria-pressed="false">Modo oscuro</button> <script> const boton = document.querySelector('.boton-tema'); boton?.addEventListener('click', () => { const oscuro = document.documentElement.classList.toggle('oscuro'); boton.setAttribute('aria-pressed', String(oscuro)); }); </script>

Astro junta estos scripts, los optimiza y los incluye una sola vez, aunque uses el componente en diez sitios. Es JavaScript normal, el mismo que usas con el DOM y los eventos.

Islas: componentes de otros frameworks

Si necesitas un componente hecho con React, Vue, Svelte o Preact (un configurador, un mapa), Astro lo pinta como HTML sin JavaScript. Para que funcione en el navegador, le pones una directiva client: y ese componente pasa a ser una isla: una parte interactiva dentro de una página estática.

DirectivaCuándo carga su JavaScript client:loadNada más abrir la página. Para lo que se usa enseguida client:idleCuando el navegador termina lo más urgente client:visibleCuando el componente aparece en pantalla al bajar client:media="(max-width: 50em)"Solo si se cumple la media query client:only="react"Solo en el navegador; no se genera HTML al construir

--- // Antes: npx astro add react import Configurador from '../components/Configurador.jsx'; --- <Configurador client:visible />

Sin directiva no hay JavaScript. Es la idea de Astro: la página llega como HTML y solo se carga el código de las partes que lo necesitan. Para un botón o un menú, el <script> de arriba basta: no hace falta un framework.

Transiciones entre páginas

Con <ClientRouter /> en el <head> del layout, Astro cambia de página sin recargarla entera y anima el cambio (un fundido, por defecto):

--- // src/layouts/Base.astro import { ClientRouter } from 'astro:transitions'; --- <head> <!-- … --> <ClientRouter /> </head>

Para que la portada de la tarjeta «vuele» hasta su sitio en la página del proyecto, dale el mismo transition:name en las dos páginas:

<!-- En la tarjeta (portada) --> <Image src={portada} alt={textoAlternativo} width={600} transition:name={`portada-${proyecto.id}`} /> <!-- En la página del proyecto --> <Image src={proyecto.data.portada} alt={proyecto.data.textoAlternativo} width={1200} transition:name={`portada-${proyecto.id}`} />

Practica

Pon las portadas de tus proyectos en src/assets/ y añade portada: image() al esquema. Pinta la portada con <Image /> en la tarjeta (width={600}) y en la página del proyecto (width={1200}). Mira en las DevTools, pestaña Red, qué formato y qué peso llegan. Añade <ClientRouter /> al layout y el mismo transition:name a las dos imágenes. Crea BotonTema.astro con su <script> y comprueba que sigue funcionando después de cambiar de página (si no, usa astro:page-load).

Errores frecuentes

Poner las fotos en public/ y usar <img>: llegan a tamaño original, sin WebP y sin width ni height. Olvidar alt en <Image />: es un error al construir. Poner client:load a todo: se pierde la ventaja de Astro. Elige client:visible o client:idle si no corre prisa. Repetir el mismo transition:name en dos elementos de la misma página. Seguir un tutorial con <ViewTransitions />: desde Astro 6 se llama <ClientRouter />.

Astro
18

Astro a fondo 3, imágenes, islas y transiciones

Un portafolio de diseño son, sobre todo, imágenes.

Código del tema: <ClientRouter />

📘 Teoría

Introducción

Un portafolio de diseño son, sobre todo, imágenes. Astro las convierte a WebP, les pone el tamaño y la carga diferida, y te deja añadir interacción solo donde hace falta. Esta página parte del portafolio de las dos anteriores.

Imágenes: src/ o public/

DóndeQué hace AstroPara
src/assets/ (o cualquier carpeta de src/)La optimiza: formato, tamaño, width y heightFotos y portadas de proyectos
public/Nada: la copia tal cualFavicon, imágenes para redes, ficheros que no se tocan

Las de src/ se importan y se pintan con el componente <Image />:

---
import { Image } from 'astro:assets';
import retrato from '../assets/retrato-lucia.jpg';
---
<Image src={retrato} alt="Lucía Prieto en su estudio, delante de un tablero de bocetos" width={480} />

El HTML que genera:

<img src="/_astro/retrato-lucia.B7x2.webp" alt="Lucía Prieto en su estudio…"
     width="480" height="600" loading="lazy" decoding="async">
  • Convierte a WebP y reduce la imagen al ancho que pides (aquí, 480 px).
  • Escribe width y height: el navegador reserva el hueco y la página no salta al cargar.
  • alt es obligatorio. Si la imagen es decorativa, alt="".
  • Añade loading="lazy": las imágenes que están más abajo se cargan al acercarte. Para la imagen grande de arriba del todo, pon loading="eager".

En las colecciones, image() en el esquema (página anterior) hace que proyecto.data.portada sea una imagen lista para <Image />:

<Image src={proyecto.data.portada} alt={proyecto.data.textoAlternativo} width={1200} />

Para ofrecer varios formatos (AVIF, que pesa menos, y WebP para el resto), <Picture />:

---
import { Picture } from 'astro:assets';
import cartel from '../assets/cartel-jazz.png';
---
<Picture src={cartel} formats={['avif', 'webp']} alt="Cartel del festival Jazz en la Alameda" width={800} />

En Markdown, las imágenes con ruta relativa también se optimizan: !Boceto del cartel. Con ruta desde / (/imagenes/boceto.jpg) salen de public/ y no se tocan.

JavaScript en el navegador: <script>

Lo que va entre --- se ejecuta al construir. Para que algo pase en el navegador (un botón, un menú que se abre), un <script> en el componente:

---
// src/components/BotonTema.astro
---
<button type="button" class="boton-tema" aria-pressed="false">Modo oscuro</button>

<script>
  const boton = document.querySelector('.boton-tema');
  boton?.addEventListener('click', () => {
    const oscuro = document.documentElement.classList.toggle('oscuro');
    boton.setAttribute('aria-pressed', String(oscuro));
  });
</script>

Astro junta estos scripts, los optimiza y los incluye una sola vez, aunque uses el componente en diez sitios. Es JavaScript normal, el mismo que usas con el DOM y los eventos.

Islas: componentes de otros frameworks

Si necesitas un componente hecho con React, Vue, Svelte o Preact (un configurador, un mapa), Astro lo pinta como HTML sin JavaScript. Para que funcione en el navegador, le pones una directiva client: y ese componente pasa a ser una isla: una parte interactiva dentro de una página estática.

DirectivaCuándo carga su JavaScript
client:loadNada más abrir la página. Para lo que se usa enseguida
client:idleCuando el navegador termina lo más urgente
client:visibleCuando el componente aparece en pantalla al bajar
client:media="(max-width: 50em)"Solo si se cumple la media query
client:only="react"Solo en el navegador; no se genera HTML al construir
---
// Antes: npx astro add react
import Configurador from '../components/Configurador.jsx';
---
<Configurador client:visible />

Sin directiva no hay JavaScript. Es la idea de Astro: la página llega como HTML y solo se carga el código de las partes que lo necesitan. Para un botón o un menú, el <script> de arriba basta: no hace falta un framework.

Transiciones entre páginas

Con <ClientRouter /> en el <head> del layout, Astro cambia de página sin recargarla entera y anima el cambio (un fundido, por defecto):

---
// src/layouts/Base.astro
import { ClientRouter } from 'astro:transitions';
---
<head>
  <!-- … -->
  <ClientRouter />
</head>

Para que la portada de la tarjeta «vuele» hasta su sitio en la página del proyecto, dale el mismo transition:name en las dos páginas:

<!-- En la tarjeta (portada) -->
<Image src={portada} alt={textoAlternativo} width={600} transition:name={`portada-${proyecto.id}`} />

<!-- En la página del proyecto -->
<Image src={proyecto.data.portada} alt={proyecto.data.textoAlternativo} width={1200}
       transition:name={`portada-${proyecto.id}`} />

Cada nombre solo puede aparecer una vez en cada página; por eso lleva el id del proyecto. Otras opciones: transition:animate="slide" (o "none") y transition:persist, que mantiene un elemento sin cambiar entre páginas (un reproductor de audio, por ejemplo).

Con <ClientRouter /> la página no se recarga, así que un <script> se ejecuta solo la primera vez. Si tiene que preparar algo en cada página, mételo en el evento astro:page-load:

document.addEventListener('astro:page-load', () => {
  const boton = document.querySelector('.boton-tema');
  // … lo mismo que antes
});

El navegador respeta la preferencia de «reducir el movimiento» del sistema: con ella activada, Astro no anima.

Practica

  1. Pon las portadas de tus proyectos en src/assets/ y añade portada: image() al esquema.
  2. Pinta la portada con <Image /> en la tarjeta (width={600}) y en la página del proyecto (width={1200}). Mira en las DevTools, pestaña Red, qué formato y qué peso llegan.
  3. Añade <ClientRouter /> al layout y el mismo transition:name a las dos imágenes.
  4. Crea BotonTema.astro con su <script> y comprueba que sigue funcionando después de cambiar de página (si no, usa astro:page-load).

Errores frecuentes

  • Poner las fotos en public/ y usar <img>: llegan a tamaño original, sin WebP y sin width ni height.
  • Olvidar alt en <Image />: es un error al construir.
  • Poner client:load a todo: se pierde la ventaja de Astro. Elige client:visible o client:idle si no corre prisa.
  • Repetir el mismo transition:name en dos elementos de la misma página.
  • Seguir un tutorial con <ViewTransitions />: desde Astro 6 se llama <ClientRouter />.

¿Qué es esto?

Soy Cristian Eslava y a veces hago webs para procrastinar yo y vosotros. culTest

La hice en febrero de 2026 para facilitar el aprendizaje de mis alumnos. La idea es aprender desarrollo web practicando y que el proyecto siga creciendo con nuevos temas, tests y retos.

Está inspirada en MDN, W3Schools, CodePen, Manz y muchos otros sitios de documentación sobre desarrollo web. Quería combinar teoría útil, ejemplos ejecutables, retos y el sistema de tests que ya tenía en culTest. culTest

Si te gustó, si no te gustó o si quieres escribirme, puedes hacerlo en cristianeslava@gmail.com