Manejo de assets en Astro: importaciones optimizadas vs carpeta public/

DUGLAS MORENODUGLAS MORENO 👁 17

Introducción

Cuando trabajás con Astro, uno de los temas que más dudas genera es cómo manejar los archivos estáticos: imágenes, fuentes, iconos o cualquier otro recurso que no sea código. Astro ofrece dos caminos principales: importar el asset directamente desde el código (lo que activa la optimización del bundler) o colocarlo dentro de la carpeta public/ y referenciarlo mediante una URL relativa. Cada enfoque tiene ventajas y compromisos en cuanto a rendimiento, caché y facilidad de uso.

Este artículo está pensado para desarrolladores que ya conocen los conceptos básicos de Astro y quieren profundizar en la forma en que el framework gestiona los assets. Al terminar de leer, vas a saber:

  • Qué ocurre bajo el capó cuando importás un asset.
  • Cómo funciona la carpeta public/ y por qué es útil.
  • Cuál es la diferencia en el tamaño del bundle, en los headers de caché y en la procesión de imágenes.
  • Cuándo conviene usar cada método y cuáles son los errores más habituales.

¿Qué son los assets en Astro?

En el contexto de Astro, un asset es cualquier archivo que no se trata como componente o módulo de JavaScript/TypeScript: imágenes (PNG, JPEG, SVG, WebP), fuentes (WOFF, WOFF2), archivos de datos (JSON, CSV) o incluso archivos de estilos que se importan directamente. Astro se basa en Vite para el bundling, por lo que la forma en que se manejan los assets sigue en gran medida las convenciones de ese herramienta.

Hay dos lugares donde podés colocar tus assets:

  1. Dentro del árbol de fuentes (src/) y importarlos con la sintaxis de ES modules.
  2. Dentro de la carpeta estática public/ y referenciarlos mediante una URL pública.

Ambos enfoques terminan generando una URL que el navegador puede solicitar, pero el proceso que lleva a esa URL es distinto.

Importaciones optimizadas: asset como módulo

Cuando importás un asset desde tu código Astro, Vite lo trata como un módulo. El proceso incluye:

  • Resolución de la ruta relativa al archivo.
  • Aplicación de plugins de transformación (por ejemplo, el plugin de imágenes de Vite puede generar versiones optimizadas, crear srcset y asignar un hash de contenido al nombre del archivo).
  • Inclusión del archivo en el gráfico de dependencias del bundle, lo que significa que va a ser copiado al directorio de salida (dist/) con un nombre que contiene un hash basado en su contenido.
  • Devolución de la URL pública del archivo como valor del módulo importado.

Ejemplo básico de importación de imagen

---
import fotoPerfil from '../assets/foto-perfil.jpg';
---

<img src={fotoPerfil} alt='Foto de perfil' width={200} height={200} />

En este ejemplo, fotoPerfil no es una cadena de texto cualquiera; es la URL pública que Vite genera después de procesar la imagen. Si la imagen se llama foto-perfil.jpg y su hash es a1b2c3d4, el valor de fotoPerfil será algo como /_astro/foto-perfil.a1b2c3d4.jpg.

Salida esperada en el HTML generado

El HTML resultante será similar a:

<img src=\''/_astro/foto-perfil.a1b2c3d4.jpg\'' alt=\'Foto de perfil\'' width=\'200\'' height=\'200\'' />

Nota: los atributos usan comillas simples porque en nuestro ejemplo evitamos comillas dobles para facilitar el escaping en JSON.

Ventajas de la importación

  • Hash de contenido: el nombre del archivo incluye un hash que cambia cuando el contenido cambia, lo que invalidará automáticamente la caché del navegador cuando el asset se actualice.
  • Optimización automática: Vite puede aplicar plugins que compriman imágenes, conviertan SVG a JSX o generen múltiples resoluciones para srcset.
  • Tree shaking: si un asset se importa pero nunca se usa en el build final, Vite puede excluirlo del output.
  • Tipado y autocompletado: en TypeScript, el módulo importado tiene tipos que facilitan el uso (por ejemplo, import.meta.glob devuelve un objeto con las URLs).

Limitaciones

  • El asset debe estar dentro del árbol de fuentes (src/ o subcarpetas). No podés importar algo que esté fuera de ese árbol sin configurar alias.
  • Cada importación crea una entrada en el gráfico de dependencias, lo que puede aumentar ligeramente el tiempo de arranque del dev server si tenés muchísimos assets importados individualmente.

La carpeta public/: assets estáticos sin procesamiento

La carpeta public/ se encuentra en la raíz del proyecto y su contenido se copia tal cual al directorio de salida (dist/), sin pasar por el bundler. Esto significa que:

  • No se aplica ningún hash de contenido.
  • No se ejecutan plugins de transformación (como optimización de imágenes).
  • La URL que se usa para referenciar el archivo es exactamente la misma que la ruta relativa a public/.

Ejemplo de uso de public/

Supongamos que tenés la siguiente estructura:

mi-proyecto/
├── public/
│   └── imágenes/
│       └── logo.svg
└── src/
    └── componentes/
        └── Encabezado.astro

En el componente Encabezado.astro podés referenciar el logo así:

---
---
<header>
    <img src='/imágenes/logo.svg' alt='Logo del sitio' />
</header>

Salida esperada en el HTML generado

El HTML resultante será:

<header>
    <img src=\''/imágenes/logo.svg\'' alt=\'Logo del sitio\'' />
</header>

Observá que la ruta comienza con una barra porque se sirve desde la raíz del sitio.

Ventajas de public/

  • Simplicidad: no necesitás importar nada, solo escribís la ruta.
  • Ideal para assets que no cambian frecuentemente o que querés servir exactamente como están (por ejemplo, archivos de verificación de dominio, manifestos, o fuentes que ya tenés optimizadas externas).
  • No afecta el tamaño del bundle de JavaScript, ya que ningún código importa esos archivos.
  • **Compatible con cualquier tipo de acceso: el navegador puede hacer una solicitud GET directa sin pasar por ningún servicio de transformación.

Limitaciones

  • Falta de hash de contenido: si reemplazás el archivo sin cambiar su nombre, el navegador podría servir una versión vieja desde su caché.
  • Sin optimizaciones automáticas: no se generan versiones WebP, no se crea srcset, no se minimizan SVG, etc.
  • Mayor riesgo de referencias rotas: si movés o renombrás el archivo dentro de public/ tenés que actualizar manualmente todas las referencias.

Comparación de rendimiento: tamaño del bundle y headers de caché

Para entender el impacto real, veamos qué ocurre en el build de producción.

Escenario A: importación de una imagen de 150 KB

Al importar foto.jpg desde src/assets/, Vite:

  1. Lee el archivo.
  2. Aplica el plugin de imágenes (si está configurado) y posiblemente lo convierte a WebP, reduciéndolo a 90 KB.
  3. Genera un nombre con hash, por ejemplo foto.a1b2c3d4.webp.
  4. Emite ese archivo en dist/_astro/.
  5. En el componente, la variable importada contiene la cadena /_astro/foto.a1b2c3d4.webp.

En el HTML final, el navegador descarga la imagen optimizada y, gracias al hash, puede cachearla agresivamente (por ejemplo, Cache-Control: max-age=31536000, immutable). Si la imagen cambia, el hash cambia y la URL es distinta, forzando una nueva descarga.

Escenario B: mismo archivo en public/

Si colocás foto.jpg dentro de public/imagenes/ y lo referenciás como /imagenes/foto.jpg:

  • El archivo se copia tal cual a dist/imagenes/foto.jpg.
  • No se aplica ninguna transformación, así que sigue pesando 150 KB.
  • La URL no contiene hash, entonces el header de caché típico que Astro agrega es algo como Cache-Control: max-age=0, must-revalidate para permitir que el navegador revise frecuentemente si hay una nueva versión (dependiendo de la configuración de vite o de un middleware personalizado).

Resultados medibles

Métrica Importación (con optimización) Public/ (sin procesamiento)
Tamaño del asset entregado 90 KB (WebP) 150 KB (original)
Header de caché max-age=31536000, immutable max-age=0, must-revalidate (o según config)
Necesidad de busting de caché automático Sí, mediante hash No, requiere versión manual en la URL
Impacto en el bundle de JS Ninguno (el asset no entra en el bundle) Ninguno (igual)
Complejidad de desarrollo Requiere importación y manejo de la variable Solo escribir la ruta

Como podés ver, la importación optimizada brinda mejores resultados en términos de peso de descarga y caché a largo plazo, mientras que public/ es más cómodo cuando el asset no necesita transformación o cuando querés un control total sobre el nombre del archivo.

Buenas prácticas para elegir el método adecuado

  1. Preferí la importación cuando el asset participe del diseño o del contenido dinámico (por ejemplo, imágenes de artículos, avatares de usuarios generados en tiempo de build, íconos que puedan variar según tema). Así obtendrás hash y optimizaciones.
  2. Usá public/ para archivos que deben tener una URL predecible y constante, como:
    • favicon.ico, manifest.json, robots.txt.
    • Fuentes auto‑hosted que ya tenés en formato optimizado y que no querés que Vite procese de nuevo.
    • Archivos de verificación de servicios (Google Search Console, etc.) que el proveedor espera en una ruta exacta.
  3. Si necesitás múltiples resoluciones de una misma imagen (srcset), la importación es el camino más directo porque podés usar el plugin de imágenes de Vite o @astrojs/image para generar automáticamente el conjunto.
  4. Evité mezclar ambos métodos para el mismo recurso (por ejemplo, importar una versión y también referenciarla desde public/), ya que generás duplicados y confusiones de caché.
  5. Configurá adecuadamente el plugin de imágenes en tu astro.config.mjs si querés que las importaciones apliquen WebP, calidad, ancho máximo, etc. Un ejemplo básico:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import image from '@astrojs/image';

export default defineConfig({
    image: {
        service: 'sharp',
        entrypoint: '@astrojs/image/server',
    },
});

Con esto, cada import foto from '../assets/foto.jpg' producirá automáticamente una versión WebP y posiblemente un conjunto de srcset según la configuración.

  1. Aprovechá import.meta.glob para importar dinámicamente varios assets sin escribir una importación por cada uno. Por ejemplo, para obtener todas las imágenes de una galería:
---
const galerias = import.meta.glob('../assets/galeria/**/*.jpg', { eager: true });
---

{#each Object.values(galerias) as foto}
    <img src={foto.default} alt='Imagen de galería' />
{/each}

El objeto devuelto por glob contiene como valor el módulo cuya propiedad default es la URL pública del archivo.

Errores comunes y cómo evitarlos

  • Olvidarse de reiniciar el dev server después de agregar un nuevo asset en public/. Aunque Astro suele detectar cambios en src/, a veces no percibe de inmediato archivos nuevos en public/. La solución es detener y volver a iniciar astro dev o forzar una recarga completa de la página.
  • Usar rutas relativas equivocadas al importar desde componentes ubicados en diferentes niveles. Recordá que la ruta es relativa al archivo que contiene la importación. Si tenés dudas, usá el prefijo src/ desde la raíz del proyecto: import logo from '@/assets/logo.svg' (si configuraste un alias @ que apunte a src/).
  • Creer que los archivos en public/ reciben hash automáticamente. Esto no ocurre; si necesitás caché a largo plazo, tenés que agregar manualmente un query string o cambiar el nombre del archivo cuando lo actualizás.
  • Aplicar transformación de imágenes a archivos que ya están optimizados, lo que puede generar un aumento inesperado de tamaño o pérdida de calidad. Revisá la configuración del plugin para excluir ciertos patrones si es necesario.
  • No especificar atributos width y height o usar size para evitar el Cumulative Layout Shift (CLS). Tanto con importaciones como con public/ es buena práctica incluir dimensiones explícitas o usar el atributo loading="lazy" cuando corresponda.

Conclusión

El manejo de assets en Astro es flexible, pero cada enfoque tiene su lugar. Las importaciones optimizadas son la opción predeterminada para la mayoría de los recursos que forman parte de la experiencia del usuario: se benefician de hash de contenido, optimizaciones automáticas y una integración fluida con el sistema de módulos de Vite. Por otro lado, la carpeta public/ brinda simplicidad y URLs predecibles, ideal para archivos estáticos que no requieren procesamiento o que deben ser accesibles mediante una ruta fija.

Al comprender cómo funciona cada método y aplicar las buenas prácticas descritas, vas a poder reducir el peso de tus páginas, mejorar la experiencia de carga y evitar problemas de caché que suelen aparecer en proyectos en crecimiento. La próxima vez que agregues una imagen, una fuente o un archivo de datos, pensá antes: ¿necesito que Astro lo transforme y le agregue un hash? Si la respuesta es sí, importalo; si no, colócalo en public/ y listo.

Con estas herramientas en tu arsenal, estarás listo para construir sitios Astro más rápidos, mantenibles y amigables tanto para los desarrolladores como para los visitantes.

Comentarios (0)

Sé el primero en comentar.

Dejá tu comentario