TL;DR
- Rebuilding a heavy landing page in Astro moved the Lighthouse performance score from 44 → 99 (measured).
- Most of the weight was JavaScript, images, and render-blocking resources. Astro's "zero JS by default" does the heavy lifting.
- I ran the work async through Claude Code. The human only owns direction and verification.
- There's a live before/after demo you can poke at (below).
This is the "make it fast" write-up. The flashy part (text exploding into particles and reassembling — glyphdust) gets one paragraph at the end.
Why Astro (what "slow" actually is)
When a landing page is slow, it's usually one of these:
- Too much JavaScript — decoration libraries, analytics tags, unused bundles.
-
Unoptimized images — full-size PNGs, no
width/height(hello CLS). -
Render-blocking — CSS/JS loaded synchronously in
<head>.
Astro attacks this structurally:
-
Island architecture: zero client JS by default; only the components you mark with
client:*get hydrated. -
Image optimization:
astro:assets<Image />/<Picture />handleswidth/height, lazy loading, and modern formats (the demo uses<Picture>). - Build-time rendering: HTML is static; CSS is easy to scope and inline.
The point isn't "switch frameworks." It's "structurally ship less JS."
What I actually did (checklist)
I used a fictional cafe ("KURADO") landing page, built twice with the same visual design: a Before (heavy) and an After (Astro).
[ ] Replace images with astro:assets <Picture /> (width/height set, modern formats auto)
[ ] Hero image loading="eager", everything else loading="lazy"
[ ] Self-host fonts; preload variable fonts with <link rel="preload" as="font" crossorigin>
[ ] Remove decorative JS, or island-ify it (client:visible, only when needed)
[ ] Scope CSS per component; drop the unused parts
[ ] async/defer third-party scripts; lazy-load where possible
Note: for self-hosted fonts use
preload, notpreconnect(preconnectis for warming up connections to external origins). The After also preloads two latin-subset variable fonts.
Nothing exotic. Just killing JS, images, and blocking moves the numbers a lot.
Results (measured)
Same look, built two ways, Lighthouse on both (my own demo, fictional cafe):
| Metric | Before (heavy) | After (Astro) |
|---|---|---|
| Performance score | 44 | 99 |
| LCP | 13.9s | 2.0s |
| TBT | 1920ms | 0ms |
| Transfer size | baseline | ~1/15 |
| CLS | ~0 | ~0 |
Honest footnote: CLS was already ~0 in the Before (it wasn't a layout problem). Not inflating numbers matters, so I only claim the metrics that actually improved (LCP / TBT / transfer size).
LCP 13.9s → 2.0s, TBT 1920ms → 0ms. That's where the felt difference is.
Running it async with Claude Code
A human doesn't need to do all of this by hand. I hand it to Claude Code and keep direction and verification for myself.
- "Replace this image set with
astro:assets<Picture />, setwidth/heightfrom intrinsic dimensions." - "Extract the XX decoration into a
client:visibleisland." - "Run Lighthouse before/after and report the diff."
The key is verifying with an independent command — not "it should be faster," but actually running Lighthouse and reading the numbers. As long as the human holds that, the implementation can be safely delegated.
Signature: glyphdust
It's a speed post, but I leave one "signature" of my work on it: glyphdust, a small OSS react-three-fiber component I built.
Note: in the demo above, glyphdust is only named as a signature — the hero itself is a performance-first static implementation (piling on effects in a speed article would defeat the point). Here's what glyphdust does on its own.
With a single scroll progress 0 → 1, it shatters text into thousands of GPU particles → recomposes them into another glyph → and finally resolves to real, selectable DOM text.
import { GlyphDust } from "glyphdust";
<GlyphDust
keyframes={[
{ type: "text", text: "KURADO", domSelector: "#headline" },
{ type: "scatter", spread: 1 },
{ type: "text", text: "FAST", dense: true, resolveToDom: true },
]}
/>
three / @react-three/fiber / react are peer dependencies (React 18+, three 0.160+). With prefers-reduced-motion or no WebGL, it falls back to your own static markup (the screen never goes blank).
The last spoonful for pages that are both fast and memorable.
Demo, and if you want a rebuild
There's a live before/after demo you can touch (my own sample, fictional cafe):
- Demo (/ , /after , /before): https://astro-rebuild-demo.pages.dev/
If you've got a slow landing page or small site and want it fast without changing how it looks, I offer a fixed-scope, fully async (no calls) package on Contra:
- Astro Rebuild Pack (Contra): https://contra.com/s/kslvEs2R-astro-rebuild-pack-turn-your-slow-site-into-a-fast-one
Core Web Vitals pass guaranteed (if it doesn't pass, I keep working until it does). Delivery is a Loom walkthrough + a staging link. No meetings.
Takeaways
- Astro "structurally reduces JS," so a slow LP's score can move a lot (44 → 99, measured).
- The work is unglamorous (kill JS, images, blocking). You can run it async with Claude Code.
- Always verify numbers by measuring. Don't inflate.
- A spoonful of signature (glyphdust) for the finish.
Top comments (0)