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.