
How to Keep an Astro Site Fast
A practical guide to static HTML, self-hosted fonts, careful animations, cache rules and honest PageSpeed testing on an Astro site.

A practical guide to static HTML, self-hosted fonts, careful animations, cache rules and honest PageSpeed testing on an Astro site.
A clean PageSpeed report is satisfying, but the green 100 is not the product. The product is a page that appears quickly on an ordinary phone, stays still while it loads and responds when someone touches it. The score is useful because it exposes regressions; it is not a permanent certificate of speed.
Astro gives a content site a good starting point. The final performance decisions are still specific: which font to preload, whether an entrance animation delays real content, and which files the server can safely cache for a year.
Astro components render to HTML at build time and add no client runtime by default. That is the framework’s documented “zero JavaScript by default” model, not a promise that every Astro site sends zero JavaScript.
Imagine a fictional portfolio with a theme switcher, a small canvas chart and animated section reveals. Those features need browser code. The useful constraint is that every script has a visible job; there is no reason to hydrate the article text as an application when HTML already expresses it.
For each interaction, ask two questions: can plain HTML do it, and does it need to run before the visitor can read? A tab can remain a real link even if JavaScript makes the transition smoother. If the script fails, navigation should still work.
If the main heading uses a web font, the first meaningful text paint may wait for that file. Self-host the required WOFF2 subset and make a genuinely critical font discoverable in the initial HTML:
<link rel="preload" as="font" type="font/woff2"
href="/fonts/mono-latin.woff2" crossorigin>
Google’s LCP guide lists a preloaded web font as one way to reduce resource-load delay for a text LCP element. The exact filename may be hashed by the build; the principle is early discovery.
Preloading everything is not an optimisation. Every preload competes for bandwidth. A Cyrillic subset can be limited to pages that use it, while images below the first viewport can remain lazy.
Entrance animation is easy to underestimate. Text with opacity: 0 is not an LCP candidate under the browser’s LCP rules, so hiding the main heading until JavaScript runs can make a fast server look slow.
Consider a fictional reveal that waits for fonts, then fades sections in one by one. It looks polished on a laptop, but on a slow phone the largest text may stay invisible for a full second. A safer version keeps the delay short, reveals immediately for reduced motion and includes a fallback that forces content visible if the module fails.
That still does not make animation free. If field data gets worse, decoration should be the first thing removed. Also avoid animating dimensions and positions that change layout. Opacity and transforms are easier to keep away from Cumulative Layout Shift than height, margin or top/left changes.
Content-hashed assets can be cached for a long time because new content produces a new URL:
location ^~ /_astro/ {
add_header Cache-Control "public, max-age=31536000, immutable" always;
try_files $uri =404;
}
HTML is different: its URL remains the same after deployment, so it should revalidate. Images may use a shorter cache, while a JSON feed that changes during the day needs something shorter again. The nginx add_header documentation is worth reading because inheritance across nested locations can surprise even a valid-looking configuration.
The rule is simple: cache according to URL stability, not file extension folklore.
The current “good” thresholds are LCP at or below 2.5 seconds, INP at or below 200 milliseconds and CLS at or below 0.1, evaluated at the 75th percentile of visits. Google explains the figures and their reasoning in its Core Web Vitals threshold guide.
These are field metrics. Lighthouse is a lab test under simulated conditions, and without real interaction it cannot measure INP. Use lab tests to catch a regression before release and real-user data to understand the experience after release. Do not combine the two into one promise.
Test the canonical live URL as well as localhost. Run mobile and desktop more than once, then inspect individual metrics and the waterfall before the overall score. Chrome’s Lighthouse scoring documentation notes that results vary with devices, network routes, extensions and other conditions.
A useful review is short:
A static site can reach a perfect lab score and lose it five minutes later under different test conditions. Chasing the last point is worthwhile only when the underlying fix improves the page for a visitor.
The durable target is simpler: HTML arrives quickly, main content does not wait for an app to boot, the page stays still and the small amount of JavaScript earns its place. That standard remains useful even when Lighthouse changes its scoring curve.