Snippet: Astro SmartImage with AVIF + WebP fallback

A short code snippet to create an Astro wrapper component that auto-generates AVIF, WebP and JPG fallbacks with lazy-loading and responsive sizes.

Code astro performance image

The problem

Astro's built-in <Image /> only outputs a single format. To get AVIF (best) → WebP (fallback) → JPG (last resort), you have to wrap a <picture> element by hand - doing that on every page becomes repetitive boilerplate.

The snippet below packages that pattern into a single reusable component.

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>

Usage

---
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"
/>

Measured results on natecue.com

  • LCP image: ~140 KB (JPG) → ~38 KB (AVIF) - ~73% reduction.
  • CLS = 0 because width/height are auto-filled by Astro from ImageMetadata.
  • Lighthouse Performance score: 92 → 98.

Notes

  • AVIF encoding is significantly slower than WebP - only run astro build at deploy time, not during development.
  • For SVG/GIF: bypass this component and use a plain <img> tag directly.
  • When deploying on Vercel/Netlify: make sure to enable long-term caching for _astro/* (immutable, 1 year).
✦ Miễn phí

Thích bài này? Nhận thêm mỗi tuần

AI workflows, marketing tips, và free tools. Không spam.

Cùng 1,200+ người đang đọc.

Không spam. Unsubscribe bất cứ lúc nào.