Learning Astro - Images & Asset Optimization
Episode 9 of 24

Learning Astro - Images & Asset Optimization

This episode covers image optimization in Astro: the Image and Picture components from astro:assets for responsive images, managing assets and static files in public and src/assets, lazy loading media, and cache control and CDN integration.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Images make up the largest share of a web page's weight. A single photo uploaded raw can be several megabytes — far bigger than all the CSS and HTML of the page. Episode 9 covers how Astro handles images and assets so your site stays fast.

Astro provides a modern asset pipeline in astro:assets: the Image and Picture components that optimize images automatically, modern formats like AVIF and WebP, and built-in lazy loading. You will also learn how to manage static files in public and the src/assets folder.

By understanding this pipeline, your pages will load much faster without adding server load.

Using astro:assets for Responsive Images

The Image Component

The Image component from astro:assets optimizes images at build time. Store the original image in src/assets, then use it:

JSGambar dengan Image
---
import { Image } from "astro:assets";
import fotoHero from "../assets/hero.jpg";
---
 
<Image
  src={fotoHero}
  alt="Foto utama halaman"
  width={1200}
  height={630}
  format="avif"
  loading="lazy"
/>

In the code above, format="avif" produces the image in a modern format, and Astro automatically generates several sizes for responsiveness. The Image component renders an <img> tag with an optimal srcset attribute.

The Picture Component for Art Direction

For full control — for example different crops for mobile and desktop — use the Picture component:

JSGambar dengan Picture
---
import { Picture } from "astro:assets";
import banner from "../assets/banner.jpg";
---
 
<Picture
  src={banner}
  formats={["avif", "webp"]}
  widths={[320, 640, 1024]}
  sizes="(max-width: 768px) 100vw, 640px"
  alt="Banner promosi"
/>

Picture renders a <picture> element with a <source> element for each format and size. The browser picks the best combination based on the viewport.

A Note on @astrojs/image

In early Astro versions, image optimization was handled by a separate package, @astrojs/image. That package is now deprecated — since Astro 3, its functionality has been absorbed into the built-in astro:assets. Always use astro:assets for new projects.

Asset Handling and Static File Management

public versus src/assets

Two places store assets with different behaviors:

  • public/: files are copied as-is to dist/ without processing. Good for favicons, robots.txt, and files that need no optimization.
  • src/assets: files go through the asset pipeline — optimized, hashed, and referenced from code.
Pembagian aset
public/favicon.svg      →  dist/favicon.svg (copy langsung)
src/assets/hero.jpg     →  dist/assets/hero-abc123.avif (dioptimasi)

The rule of thumb: put images used in code in src/assets, and files used as-is (like robots.txt) in public.

Importing Assets from Markdown

Markdown content can also use the asset pipeline by importing images in frontmatter and using getImage on the page:

JSgetImage untuk gambar dari koleksi
---
import { getImage } from "astro:assets";
import { getCollection } from "astro:content";
 
const posts = await getCollection("blog");
const hero = posts[0].data.cover;
const optimized = await getImage({ src: hero, format: "webp" });
---
 
<img src={optimized.src} alt={posts[0].data.title} />

getImage returns the optimized URL without having to use the Image component directly — flexible for dynamic images from a collection.

Lazy Loading Media and Performance Best Practices

Built-in Lazy Loading

Astro adds the loading="lazy" attribute to below-the-fold images automatically when you use astro:assets. Above-the-fold images should be priority with loading="eager" so they load immediately — for example hero images.

JSGambar hero dimuat segera
<Image src={hero} alt="Hero" loading="eager" fetchpriority="high" />

The fetchpriority="high" attribute tells the browser this image is important and should be prioritized.

Always Set Dimensions

Always provide width and height — whether in the Image component or in CSS. This prevents layout shift (content jumping when images finish loading) and keeps the Cumulative Layout Shift (CLS) value low.

Cache Control and CDN Integration

Cache Headers for Images

Built images are usually cached for a long time because their filenames contain a hash — new content gets new names. For deployments using a CDN, set the cache header:

Cache header contoh di Vercel
# vercel.json
{ "headers": [
  { "source": "/_astro/(.*)", "headers": [
    { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
  ] }
] }

Hashed filenames allow a full year of caching safely — when an image changes, the filename changes too, so the browser automatically picks up the new version.

Using a CDN

After the build, the whole dist/ folder can be served through a CDN for global distribution. You will design a more thorough CDN and cache strategy in episode 14.

Tip

Check image sizes by opening DevTools on the Network tab and sorting by size. If any image is above 200 KB, it is almost certainly worth optimizing with astro:assets.

Conclusion

Episode 9 equips you with image and asset optimization: the Image and Picture components from astro:assets for responsive images, asset management in public versus src/assets, lazy loading and load priority, and cache control for CDN integration.

The key takeaways:

  • astro:assets replaces the deprecated @astrojs/image.
  • Image produces responsive images; Picture is for art direction.
  • Store processed assets in src/assets, copied files in public.
  • Lazy loading is automatic; hero images use loading="eager".
  • Always set dimensions to prevent layout shift.
  • Hashed files can be safely cached for a year on a CDN.

In the next episode 10, we will cover forms and interactions: form handling on Astro pages, client-side interactions with hydrated components, UI validation and progressive enhancement, and form submission through serverless functions. Your interactivity starts serving real users.