Why I choose Astro for client websites in 2025
Let me be upfront about something: this isn’t a post about Astro being “blazing fast.” That framing is both true and useless. Everything is fast on a MacBook with a fiber connection and no real users. What I actually care about — what my clients actually care about — is what happens when a 54-year-old bakery owner in rural Germany opens the site on her iPhone 11 over 4G. That’s the benchmark. And that’s where Astro consistently wins.
What clients actually care about
The mistake most developers make is optimizing for things that impress other developers. Clients care about three things, in this order:
- Does it load fast enough that customers don’t leave?
- Can I update the content without calling someone?
- Will I be paying for this indefinitely?
That’s it. Nobody has ever asked me about hydration strategies or edge rendering. Those are implementation details that serve the above three concerns. Astro happens to solve all three exceptionally well, which is why I keep reaching for it on client work.
Google’s Core Web Vitals are now a ranking factor. LCP (Largest Contentful Paint) under 2.5 seconds is the target. Most WordPress sites fail this on mobile. Most Next.js marketing sites I’ve audited fail it too — not because the framework is bad, but because shipping 400KB of JavaScript for a five-page brochure site is structurally insane and Astro makes it structurally impossible to do by default.
The zero-JS default — why it matters for most sites
This is the piece that’s genuinely novel about Astro’s model. Every other major framework ships JavaScript by default. You opt out. Astro ships no JavaScript by default. You opt in.
The difference sounds subtle. It isn’t.
When a Next.js developer builds a marketing site, they have to consciously fight against the framework to avoid hydrating components that don’t need hydration. The path of least resistance is to just let React take over. With Astro, the path of least resistance is to do nothing, which means zero client-side JavaScript for static components. The default is also the correct behavior.
For a typical client site — five pages, a contact form, maybe a blog — you end up with somewhere between 0 and 8KB of JavaScript in production. Compare that to a minimal Next.js app, which routinely ships 70-120KB of framework code before you’ve written a single line of your own logic.
This isn’t an edge case. For the majority of websites that exist — portfolios, restaurants, agencies, local businesses, service providers — the content is fundamentally static. It doesn’t need a reactive runtime. Shipping one anyway is a performance tax your users pay every time they visit.
Content collections vs a CMS — when each makes sense
Astro’s content collections are genuinely good for developer-managed content. I use them for my own site, and I use them for clients who are comfortable with Markdown or whose content won’t change more than a few times per year.
Here’s how a typical content collection query looks in practice:
import { getCollection } from 'astro:content'
const posts = await getCollection('blog', ({ data }) => {
return data.featured === true
})
const sorted = posts.sort(
(a, b) => b.data.date.valueOf() - a.data.date.valueOf()
)
Type-safe, zero runtime overhead, and the schema validation catches errors at build time rather than in production. It’s a clean developer experience.
That said, content collections are not a CMS replacement for clients who need to update content themselves regularly. If your client wants to post a new menu item or write a blog post without touching a terminal, you need a headless CMS — Sanity, Contentful, or even a simple Notion-to-Astro integration. Astro works well with all of them. The framework doesn’t mandate a content strategy.
The rule I use: if a developer will manage the content, use collections. If a non-technical person needs to manage content more than once a month, reach for a headless CMS with a proper UI.
The island architecture — React where you need it, nothing where you don’t
The marketing pitch for islands is often vague. Here’s the concrete version: you can use any framework — React, Vue, Svelte, Solid — for interactive components, while the surrounding page stays as static HTML. Each interactive component is a self-contained island with its own hydration strategy.
This is what it looks like in an Astro component:
---
// Static shell — no JavaScript shipped
import ContactForm from '../components/ContactForm.tsx'
import StaticHeader from '../components/Header.astro'
---
<StaticHeader />
<main>
<section class="content">
<!-- This renders as pure HTML, zero JS -->
<h1>Get in touch</h1>
<p>I respond within 24 hours.</p>
</section>
<!-- This island hydrates only when visible in viewport -->
<ContactForm client:visible />
</main>
The client:visible directive means the React component doesn’t hydrate until the user scrolls it into view. For something like a contact form at the bottom of a page, this means it doesn’t block the critical rendering path at all. You can also use client:idle (hydrate when the browser is idle), client:load (immediate), or client:only (skip SSR entirely for components that can’t run on the server).
In practice, most client sites I build end up with one or two islands — a contact form, maybe a carousel or interactive map. Everything else is static HTML, which is exactly what it should be.
Performance numbers — what actually happens in production
Numbers from real projects, measured with Lighthouse on simulated slow 4G:
- Restaurant website (5 pages, image-heavy): Lighthouse 99, LCP 0.8s on Vercel Edge
- Agency portfolio (9 pages, video background): Lighthouse 97, LCP 1.1s on Cloudflare Pages
- Local service business (6 pages, contact form as island): Lighthouse 100, LCP 0.6s on Netlify
None of these are cherry-picked benchmark conditions. These are production sites with real content, real images (properly optimized with Astro’s built-in <Image> component), and real users. The scores hold up because there’s genuinely nothing to slow them down.
Sub-1-second LCP on edge-deployed Astro sites is achievable and repeatable. It requires discipline with images and fonts, but the framework doesn’t fight you on it.
When NOT to use Astro
This is the part most Astro proponents skip. Don’t.
Astro is the wrong tool for:
- Heavily interactive single-page applications. A project management tool, a real-time dashboard, a complex data visualization app — these are better served by a proper SPA framework. You could technically build them in Astro with a
client:onlyisland that takes over the whole page, but at that point you’re fighting the framework’s mental model. - Apps with frequent client-side route transitions with shared state. Astro has view transitions and client-side routing, but if your UX fundamentally depends on React (or Vue) managing state across route changes, stick with Next.js or Nuxt.
- Anything requiring real-time data. WebSocket-heavy apps, collaborative editing, live feeds — not Astro’s domain.
There’s no shame in using the right tool for the job. Astro is the right tool for most public-facing marketing sites, blogs, portfolios, and content-driven products. It is not a universal answer.
The professional conclusion
I’ve been building for the web long enough to be deeply skeptical of anything described as “the future of web development.” Astro doesn’t need that framing. It solves a well-defined problem — content-focused websites that need to be fast, maintainable, and cheap to run — better than anything else I’ve worked with.
The architecture enforces good defaults. The build output is predictable. The developer experience is excellent without being magic. And most importantly, clients’ websites actually perform well for their actual users on their actual devices.
That’s the whole argument. If your use case fits — and for most agency and freelance work, it does — use Astro. If it doesn’t, use something else. The framework doesn’t care either way, and that’s exactly the kind of confidence I want in a tool I’m putting production work on.