Overview

Astro’s output is HTML and CSS with no JavaScript until you add a client:* directive, so the baseline is already fast. Most regressions come from shipping that advantage away: framework components that did not need to be islands, unoptimized fonts, and late-discovered hero images. Field metrics and thresholds are in core-web-vitals.

Ship no JavaScript unless a component needs it

A component with no client:* directive renders to static HTML. If a React component has no state, no event handlers, and no browser APIs, rewrite it as an .astro file; it then ships no runtime. Add one island with the right directive for each genuinely interactive element. Directive rules are in astro-islands.

Audit the built output

astro build has no bundle-analysis flag. Inspect the result directly: list dist/_astro/ to see the generated chunks, and grep for scripts that pages should not have.

npx astro build
ls -lh dist/_astro/
grep -rl '<script' dist/ --include='*.html'

A page with no islands, no <ClientRouter />, and no scripts of your own should contain none apart from JSON-LD. For a treemap of module sizes, add a Vite bundle visualizer plugin.

Use scoped styles and global ones deliberately

Styles in an .astro component’s <style> block are scoped through a generated data-astro-cid-* attribute, so no naming convention is needed and each page loads only the CSS of the components it uses. Use <style is:global> or a global stylesheet imported in the base layout for site-wide rules, and the :global() selector only to reach child components or CMS-rendered content.

Load fonts with the Fonts API

The Fonts API (stable since Astro 6) downloads, caches, and self-hosts fonts, generates fallback metrics, and emits preload hints. Register fonts in the config and render <Font /> once in the <head>.

// astro.config.mjs
import { defineConfig, fontProviders } from "astro/config";
 
export default defineConfig({
  fonts: [{
    name: "Inter",
    cssVariable: "--font-inter",
    provider: fontProviders.google(),
    weights: ["100 900"], // variable font range
    fallbacks: ["sans-serif"],
  }],
});
---
import { Font } from "astro:assets";
---
<head><Font cssVariable="--font-inter" preload /></head>

Then use font-family: var(--font-inter). Preload only the fonts used above the fold. Built-in providers include Google, Fontsource, Bunny, Fontshare, Adobe, and local files, so a hand-written @font-face or a third-party stylesheet <link> is no longer needed.

Mark the LCP image with priority

Add priority to the hero <Image /> so Astro sets eager loading, sync decoding, and fetchpriority="high"; a hand-written preload <link> in the base layout is unnecessary. See astro-image-optimization.

Tune prefetching

The prefetch config enables data-astro-prefetch links. The default strategy is hover; tap, viewport, and load are the alternatives, and prefetchAll defaults to true when <ClientRouter /> is present. Reserve viewport and load for a few critical links, because they prefetch eagerly.