React

Immagini ottimizzate in Astro: Image, Picture e formato

In Astro le immagini usano due componenti: Image genera larghezze e un formato, Picture offre AVIF e WebP con fallback, e public/ resta fuori dalla pipeline.

In sintesi

  • Il componente Image legge il file importato, genera il srcset e scrive width e height nell’HTML.
  • Picture dichiara più formati e un fallback: il browser prende il primo che sa leggere.
  • Un file in public/ non passa dalla pipeline: resta copiato così com’è.

In Astro le immagini si ottimizzano dal componente Image di astro:assets: converte il formato, genera le larghezze richieste e scrive width e height nell’HTML, che è ciò che evita lo spostamento del layout.

Cosa fa il componente Image

Il file importato da src/assets viene letto in build: widths produce un srcset, sizes dice al browser quale scegliere e format decide il codec. Nulla di questo accade per un file in public/, che resta copiato così com’è.

npx astro sync
---
import { Image } from "astro:assets";
import hero from "../assets/hero.jpg";
---

Con Picture dichiari più formati e un fallback: il browser prende il primo che sa leggere, quindi AVIF dove c’è e JPEG altrove. Il markup esce già con source e img.

---
import { Picture } from "astro:assets";
import hero from "../assets/hero.jpg";
---

Immagini remote e cartelle

Un’immagine su un altro dominio va autorizzata nella configurazione, altrimenti Astro la rifiuta in build. La cartella conta: src/assets passa dalla pipeline, public/ no.

// astro.config.mjs
import { defineConfig } from "astro/config";

export default defineConfig({
  image: {
    domains: ["cdn.example.com"],
  },
});

Layout e spazio riservato

La prop layout scrive rapporto d’aspetto e dimensioni responsive: con constrained l’immagine non supera la colonna, con full-width occupa la pagina. In entrambi i casi lo spazio è riservato prima del caricamento.

node -e "const fs=require('fs');const d='public/blog-covers/it';const s=fs.readdirSync(d).filter(f=>f.endsWith('.png')).map(f=>fs.statSync(d+'/'+f).size);const t=s.reduce((a,b)=>a+b,0);console.log(s.length,(t/1048576).toFixed(2)+'MB',(t/s.length/1024).toFixed(1)+'KB')"

Il numero da guardare non è la media ma la coda: la copertina più grande resta sotto 78 KB, quindi l’intero archivio sta in un budget da 80 KB per file. Una media bassa con una coda a 400 KB racconta un’altra storia, e il grafico della distribuzione lo mostra prima del totale.

Un’immagine generata per la condivisione

---
import { getImage } from "astro:assets";
import cover from "../assets/cover.jpg";

const og = await getImage({ src: cover, width: 1200, height: 630, format: "png" });
---

getImage restituisce un oggetto con src e gli attributi già calcolati: serve quando l’immagine non sta in un tag img, come nel meta og:image o in un background-image. La misura 1200×630 è quella che le anteprime social leggono senza ritagliare.

La regola di decisione

Regola: un file passa da src/assets quando è il layout a decidere la larghezza; resta in public/ quando è già nella misura finale e non cambia mai, come una favicon o un logo. Il discriminante è chi conosce il contenitore: il componente lo conosce, il foglio di stile no.

L’errore da evitare è una scala di larghezze copiata da un altro progetto: [320, 640, 960, 1280, 1600] su un’immagine che il layout mostra a 320 px produce quattro file che nessun browser chiederà. Parti dalla larghezza massima reale e scendi a metà.

Il limite da mettere in conto

La conversione costa tempo di build: ogni larghezza è un file in più, e una pagina con venti immagini su cinque larghezze produce cento file. Su un hosting che non conserva la cache di build il primo caricamento è più lento del previsto.

Errori che si vedono

  • Importare un file da public/: il percorso è una stringa, il componente non può misurarla e non genera nulla.
  • Dimenticare alt: il componente compila, ma l’immagine resta muta per chi usa uno screen reader.
  • Togliere il fallback dopo aver scelto AVIF: i browser che non lo leggono mostrano il vuoto.
  • Scrivere width a mano su un’immagine generata: il valore viene sovrascritto dal componente.
  • Dichiarare un sizes generico quando l’immagine occupa mezza colonna: il browser scarica la larghezza sbagliata e il srcset non serve a nulla.

Le immagini del catalogo si sfogliano per categoria: image raccoglie i componenti, saastro porta gli esempi pronti e il blocco hero è il posto in cui l’immagine grande pesa di più. Le collezioni di contenuti sono in content collections in Astro, l’idratazione in isole interattive in Astro, lo stesso problema in un altro framework in immagini con next/image, il confronto in Astro vs Next.js.

Domande frequenti

Devo spostare tutte le immagini in src/assets?

Solo quelle che vuoi far convertire: un file in src/assets passa dalla pipeline e riceve srcset e dimensioni. Un’immagine in public/ resta servita così com’è, utile per favicon e file già ottimizzati.

AVIF o WebP?

AVIF comprime meglio ma richiede più tempo di codifica e non è letto da browser molto vecchi. La scelta sicura è entrambi, con Picture e un fallback JPEG che copre il resto.

Perché la build è diventata lenta?

Perché ogni larghezza e ogni formato è un file da generare. Riduci l’elenco di widths a quelle che il layout usa davvero e la build torna proporzionata al numero di immagini.

Come gestisco le immagini da un CDN?

Aggiungendo il dominio in image.domains o in remotePatterns della configurazione. Astro scarica l’originale in build e genera le varianti: il CDN serve solo il file di partenza.

Image o Picture?

Image quando ti basta un formato, Picture quando vuoi AVIF e WebP con un fallback. Picture produce più file, quindi serve un motivo per pagarlo.