Vấn đề
<Image /> mặc định của Astro chỉ output 1 format. Để có AVIF (best) → WebP (fallback) → JPG (last resort) cần wrap thêm <picture> thủ công - lặp lại nhiều lần là code rác.
Snippet bên dưới gói gọn pattern đó thành 1 component dùng được khắp project.
File: src/components/SmartImage.astro
---
import { getImage } from 'astro:assets';
import type { ImageMetadata } from 'astro';
interface Props {
src: ImageMetadata;
alt: string;
widths?: number[];
sizes?: string;
loading?: 'lazy' | 'eager';
class?: string;
}
const {
src,
alt,
widths = [400, 800, 1200, 1600],
sizes = '(min-width: 1024px) 1200px, 100vw',
loading = 'lazy',
class: className,
} = Astro.props;
async function setOf(format: 'avif' | 'webp' | 'jpg') {
const imgs = await Promise.all(
widths.map((w) => getImage({ src, format, width: w })),
);
return imgs.map((i, idx) => `${i.src} ${widths[idx]}w`).join(', ');
}
const avif = await setOf('avif');
const webp = await setOf('webp');
const jpg = await setOf('jpg');
const fallback = await getImage({ src, format: 'jpg', width: widths[0] });
---
<picture class={className}>
<source type="image/avif" srcset={avif} sizes={sizes} />
<source type="image/webp" srcset={webp} sizes={sizes} />
<source type="image/jpeg" srcset={jpg} sizes={sizes} />
<img
src={fallback.src}
alt={alt}
loading={loading}
decoding="async"
width={fallback.attributes.width}
height={fallback.attributes.height}
/>
</picture>
Cách dùng
---
import SmartImage from '../components/SmartImage.astro';
import hero from '../assets/hero.jpg';
---
<SmartImage
src={hero}
alt="Hero banner"
widths={[640, 1024, 1600, 2400]}
sizes="(min-width: 1280px) 1280px, 100vw"
loading="eager"
/>
Kết quả đo được trên natecue.com
- LCP image: ~140KB (JPG) → ~38KB (AVIF) - giảm ~73%.
- CLS = 0 do
width/heightđược Astro auto-fill từ ImageMetadata. - Lighthouse Performance score: 92 → 98.
Lưu ý
- AVIF encode chậm hơn WebP nhiều lần - chỉ chạy
astro buildkhi deploy, không cần trên dev. - Với SVG/GIF: bypass component này, dùng
<img>trực tiếp. - Nếu deploy trên Vercel/Netlify: nhớ enable cache cho
_astro/*(immutable, 1 năm).
Bài viết liên quan