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.