Astro starts with a huge advantage: by default it ships zero JavaScript to the client. But that alone doesn’t guarantee good Core Web Vitals; the rest comes down to deliberate decisions.

LCP: the image that matters

The largest element in the viewport is usually an image. Use Astro’s <Image /> component to serve modern formats and correct sizes, and preload only the hero image.

---
import { Image } from 'astro:assets';
import hero from '../assets/hero.jpg';
---
<Image src={hero} alt="..." loading="eager" fetchpriority="high" />

CLS: reserve the space

Layout shift comes from elements that load without dimensions. Set width and height on images and iframes, and avoid injecting content above the fold after the first render.

INP: less JS, better INP

This is where Astro shines. Every component you avoid hydrating removes work from the main thread. Use islands only where there’s real interaction:

<Carousel client:visible />

client:visible delays hydration until the component scrolls into view.

Fonts without flicker

Download fonts and serve them locally with font-display: swap. No blocking the render while waiting for Google Fonts.

Result

With these rules, the projects I ship land in the green on PageSpeed with no tricks: it’s the natural consequence of sending less to the browser.