Images
Astro has a built-in image optimisation pipeline. Always use it - never use plain <img> tags for content images.
<Picture> component
Section titled “<Picture> component”---import { Picture } from 'astro:assets';import heroImg from '../assets/hero.jpg';---
<Picture src={heroImg} widths={[480, 768, 960, 1280, 1440]} sizes=”100vw” formats={['avif']} quality={45} alt=”Descriptive alt text” loading=”eager” fetchpriority=”high”/>Use <Picture> (not <Image>) - it generates <source> elements for multiple formats and sizes.
Settings by use case
Section titled “Settings by use case”Hero / full-width images (LCP)
Section titled “Hero / full-width images (LCP)”<Picture src={img} widths={[480, 768, 960, 1280, 1440]} sizes=”100vw” formats={['avif']} quality={45} alt=”...” loading=”eager” fetchpriority=”high”/>loading=”eager” + fetchpriority=”high” ensures the browser starts downloading this image immediately. Required for good LCP (Largest Contentful Paint) scores.
Why these widths? Modern phones have 2-3× device pixel ratio (DPR). A 390px iPhone at 2× DPR needs a 780px image — without a ~960px breakpoint the browser jumps straight to the 1280px variant. The [480, 768, 960, 1280, 1440] set covers 1× and 2× mobile, tablets, and desktop without unnecessary gaps.
Why quality 45? AVIF compresses so well that quality 45 is visually indistinguishable from 65 at typical viewing sizes, while producing ~30% smaller files.
Carousel / slideshow images
Section titled “Carousel / slideshow images”All slides except the first one must start hidden so the browser doesn’t load 5+ images on initial page load:
{slides.map(({ img, name }, i) => ( <div class:list={[ 'carousel-slide absolute inset-0 transition-opacity duration-1000', i === 0 ? 'opacity-100' : 'hidden opacity-0 pointer-events-none', ]} > <Picture src={img} alt={name} widths={[480, 768, 960, 1280, 1440]} sizes=”100vw” formats={['avif']} quality={45} loading={i === 0 ? 'eager' : 'lazy'} fetchpriority={i === 0 ? 'high' : 'auto'} /> </div>))}display: none (Tailwind hidden) prevents the browser from loading the image. When the carousel navigates to a slide, remove hidden first, then fade in via double-requestAnimationFrame so the opacity CSS transition fires correctly:
function goTo(index) { const prev = current; current = (index + slides.length) % slides.length;
slides[prev].classList.remove('opacity-100'); slides[prev].classList.add('opacity-0', 'pointer-events-none');
slides[current].classList.remove('hidden'); // triggers image load requestAnimationFrame(() => { requestAnimationFrame(() => { // wait for display:block paint slides[current].classList.remove('opacity-0', 'pointer-events-none'); slides[current].classList.add('opacity-100'); }); });}Grid cards / smaller images
Section titled “Grid cards / smaller images”<Picture src={img} widths={[375, 640, 900]} sizes=”(min-width: 1024px) 33vw, (min-width: 640px) 50vw, 100vw” formats={['avif']} quality={55} alt=”...” loading=”lazy”/>All other images
Section titled “All other images”loading=”lazy”Never use loading=”eager” on images below the fold - it hurts performance.
Image formats
Section titled “Image formats”Always use formats={['avif']}. AVIF is typically 50-80% smaller than JPEG at equivalent quality. All modern browsers support it. Astro falls back to the original format automatically for older browsers.
Alt text
Section titled “Alt text”Every image must have meaningful alt text. Empty alt="" is only correct for decorative images that add no information.
Bad: alt="image", alt="photo", alt="img_1234.jpg"
Good: alt="Swaroop Bungalow office exterior in Panchgani"
Where to store images
Section titled “Where to store images”src/assets/- images that go through Astro’s optimisation pipeline (use with<Picture>)public/- images served as-is (OG image, favicon, logo, files linked directly)
OG image (public/og-image.jpg)
Section titled “OG image (public/og-image.jpg)”- Not processed by Astro - lives in
public/and is served as-is - Must be exactly 1200 x 630 pixels
- Keep under 300 KB (WhatsApp limit)
- After changing it, use the Facebook debugger to clear the social preview cache