Skip to content

Frontend

Astro Image Component — AVIF + WebP + Responsive in 4 Lines

The Image component from astro:assets is the move. Auto-generates AVIF + WebP with original fallback, responsive sizes via srcset, lazy by default. Aidxn's marketing pages ship 60kb hero images that would be 800kb without it.

🖼️ 🎯

Image optimization is the biggest ROI lever in web performance. A 1.5MB unoptimized hero image kills your LCP, increases your CLS, and tanks conversion rates. Modern image formats (AVIF, WebP) cut file size by 60–80%, but generating them manually for mobile / tablet / desktop across 3 image formats is miserable. Astro's Image component from astro:assets solves this: one import, one line per image, and the build system generates AVIF + WebP + original fallback, responsive srcset attributes, lazy loading by default. No external service. No API calls. No vendor lock-in. At Aidxn, we ship hero images at 60kb (was 800kb before optimization). Blog images go from 500kb to 40kb. That's the difference between LCP of 2s and 400ms. This is the pattern to use on every Astro site.

Why Image Optimization Matters

The web got image-heavy around 2015. Most product marketing sites now load 20–40 images per page. A typical unoptimized PNG or JPG from Figma is 1–2MB. At 40 images, that's 40–80MB of downloaded data per page. LCP (Largest Contentful Paint) is now the most visible Core Web Vital — Google ranks slow sites lower, and slow sites convert worse. The math is simple: optimized images == faster page load == higher conversion. Image CDNs (Cloudinary, Bunny, R2 — covered in another post) solve the transform problem at scale. But for static sites, marketing pages, and Astro projects, the Image component is your first line of defense. It cuts file size by 60–80% with zero external dependencies.

What the Image Component Does

The Image component from astro:assets is a build-time optimization tool. Import an image, pass it to the component, and Astro's build system automatically generates:

  • AVIF variant — smallest, newest format, browser fallback handles older Safari
  • WebP variant — good compression, widely supported (all modern browsers)
  • Original fallback — JPG or PNG for ancient browsers (IE11, old Android)
  • Responsive srcset — multiple widths (320px, 640px, 1280px, etc.), browser picks the right one
  • Lazy loading — native loading="lazy" by default, images below the fold load on demand

All of this happens at build time. No runtime overhead. No JavaScript. The output is a standard <img srcset="..."> tag with intelligently-sized variants. File size savings: typically 70–80% of the original.

The 4-Line Setup

Start with an image file in your src/assets/ directory (or any local path). Then:

import {{ Image }} from 'astro:assets';
import heroImage from '../assets/hero.png';

<Image
  src={heroImage}
  alt="Hero section background"
  width={1200}
  height={675}
/>

That's it. The build generates 5–6 image variants (AVIF + WebP + fallback at multiple widths), outputs them to dist/, and emits a single responsive <img> tag. Inspect the HTML and you'll see:

<img
  src="/_astro/hero.hash.webp"
  srcset="/_astro/hero.hash.320w.avif 320w, /_astro/hero.hash.640w.webp 640w, ..."
  alt="Hero section background"
  width="1200"
  height="675"
  loading="lazy"
/>

Browsers that support AVIF use that. Browsers that support WebP use that. Old browsers fall back to the original format. The browser's native image loading algorithm picks the right size based on the viewport width. Lazy loading is on by default — images below the fold don't load until the user scrolls.

Three Real Patterns

Pattern 1: Hero Image (Build-Time Optimization)

Hero images are the biggest ROI. A 2400×1350 hero at 4x the device size is overkill. The Image component downsamples automatically. Define the display size (1200×675 on desktop), and Astro generates 320w / 640w / 1280w variants. A typical Figma export (2MB uncompressed PNG) becomes ~120kb AVIF + ~180kb WebP + ~280kb PNG fallback. Total srcset is ~50kb (the browser downloads only one variant). LCP improvement: 1.2s → 400ms.

import {{ Image }} from 'astro:assets';
import heroImage from '../assets/hero.png';

<Image
  src={heroImage}
  alt="Product hero"
  width={1200}
  height={675}
  loading="eager"
  {/* eager = above-the-fold, high priority */}
/>

Set loading="eager" for images above the fold. Everything else defaults to loading="lazy" (correct behavior). On the server-side render, Astro calculates the optimal sizes attribute automatically. For hero sections, the browser knows the image is 100% of viewport width on mobile, 80% on desktop, etc.

Pattern 2: Gallery (Responsive Aspect Ratio)

Gallery images need responsive layouts. A gallery grid on mobile is 1 column (100% width), on tablet 2 columns (50% width), on desktop 3 columns (33% width). Astro's Image component respects the CSS layout — it calculates the right srcset based on the actual rendered width, not the viewport width. The sizes prop tells the browser exactly how you're laying out the image:

<Image
  src={galleryImage}
  alt="Portfolio piece"
  width={800}
  height={600}
  sizes="(max-width: 640px) 100vw, (max-width: 1200px) 50vw, 33vw"
  {/* mobile: 100vw, tablet: 50vw, desktop: 33vw */}
/>

This tells the browser: on small screens, the image is 100% of the viewport width, so download a 320–640w variant. On tablets, it's 50% of viewport (so 640–1200w). On desktop, it's 33% (so 1200w+). The browser picks the smallest variant that covers the rendered size with a small safety margin. Typical savings: 40–60% file size compared to serving one "large" image to all devices.

Pattern 3: Logo or Small Icon (No Lazy Load)

Logos and small images don't need responsive variants — they're tiny. Astro still optimizes format (AVIF + WebP) but generates fewer size variants. Set width and height to the actual pixel dimensions:

<Image
  src={logo}
  alt="Aidxn Design logo"
  width={120}
  height={120}
  loading="eager"
  {/* No sizes prop — image is fixed width */}
/>

Astro sees the image is small and generates only 1–2 variants. A 150kb PNG logo becomes ~20kb. Load time: negligible. This is fine for always-on UI elements (header logos, favicons, etc.).

Common Traps and How to Avoid Them

Trap 1: LQIP (Low-Quality Image Placeholder)

Some teams use LQIP — a blurred placeholder that loads before the full image. Astro's Image component doesn't generate LQIP by default (you'd need a custom integration). For marketing sites where perceived performance matters more than strict UX, you can add LQIP via a blurDataURL from an external service (like plaiceholder). But typically, native lazy loading + WebP / AVIF is fast enough that LQIP feels unnecessary. Browser support for lazy loading is universal (95%+), so images pop in ~50–100ms after the user scrolls into view. If you need LQIP, it's one more dependency — weigh the UX gain against the build-time complexity.

Trap 2: Art Direction (Different Images per Breakpoint)

Art direction means "serve a different image entirely on mobile" — e.g., a wide product shot on desktop, a tall portrait crop on mobile. The Image component doesn't support art direction natively. For that, you need the <picture> element or a custom wrapper. Example:

<picture>
  <source media="(max-width: 640px)" srcset={mobileImage.src} />
  <Image src={desktopImage} alt="Product" width={1200} height={675} />
</picture>

This is rare outside of e-commerce or highly-curated marketing pages. For most sites, a single responsive image with good composition (important content visible on all widths) is sufficient.

Trap 3: Lazy Load Budget

Lazy loading is great for images below the fold, but not above. If your hero image is set to loading="lazy" (the default), it delays LCP by 100–300ms — users see blank space while the browser fetches it. Always set loading="eager" for above-the-fold images. For images further down the page, leave the default loading="lazy". The rule: if you can see it without scrolling, it should load eagerly.

Trap 4: Image Dimensions Are Required

Never skip width and height. Astro needs them to calculate the responsive srcset and prevent Cumulative Layout Shift (CLS). If you don't know the dimensions, get them from Figma, your design tool, or the source file. Guessing or using 0/0 breaks the layout.

Trap 5: Remote Images (URLs)

The Image component works only with local files (relative paths in src/assets/). For remote images (user uploads, third-party APIs), use a standard <img> tag with a CDN like Cloudinary or Bunny.net. Astro can't optimize at build time if the image isn't available at build time.

Six FAQs

Can I use the Image component with CSS background-image?

No. The Image component outputs an <img> tag. For CSS backgrounds, you'd use a standard <img> or <picture>, or import the image file directly and use its path. This is rarely necessary — semantic <img> with alt text is preferred for accessibility.

What if I need to support OG image generation (dynamic social previews)?

OG images are dynamic (user names, prices, data) and generated at request time. Use @vercel/og or a similar service (Cloudinary Dynamic URLs). Astro's Image component is for static images that don't change per request.

Can I resize images in JavaScript after the build?

No. The Image component generates srcset at build time. If you need runtime resizing, use a CDN. For static sites, all resizing happens at build time.

Does the Image component work with PNG, WebP, SVG, or only JPG?

It works with JPG, PNG, WebP, GIF, TIFF. SVGs are vectors — Astro passes them through unchanged (no optimization needed). For SVGs, import and use directly: <img src={svgIcon.src} alt="..." />.

How much faster are AVIF + WebP really?

Depends on the image. Photos compress well: 60–70% smaller. Graphics / illustrations compress less well: 30–50% smaller. A rule of thumb: AVIF is ~30% smaller than WebP, WebP is ~25% smaller than JPG. On a typical hero image, expect ~70% total reduction from original PNG/JPG.

Can I customize the sizes Astro generates?

Yes. By default, Astro generates 320w / 640w / 1280w / original. You can override via the widths prop (array of pixel widths) or via astro.config.mjs (global setting). For most projects, the defaults are fine.

The Bottom Line

The Image component from astro:assets is the easiest image optimization you'll ever do. Four lines of code. Build-time generation (no runtime cost). Automatic AVIF + WebP + fallback. Responsive srcset. Lazy loading by default. File size savings of 60–80%. It's table stakes for any Astro site shipping images. Every image that passes through the component is faster and more performant. Use it for heroes, galleries, team photos, case studies — anything. For remote images or dynamic content, upgrade to a CDN (see the post on Cloudinary vs Bunny vs R2). For static Astro sites, the Image component is all you need. See Aidxn Design performance consulting for image optimization audits and compression strategies.

Let us make some quick suggestions?

Please provide your full name.
Please provide your phone number.
Please provide a valid phone number.
Please provide your email address.
Please provide a valid email address.
Please provide your brand name or website.
Please provide your brand name or website.