The one idea — zero JS by default
An Astro page is a sea of static HTML. Islands are the bits that get hydrated — the rest ships zero JS. An SPA, by contrast, hydrates everything.
Astro components (.astro) render to static HTML with no client-side runtime.
A framework component (React, Svelte, Vue…) only ships JavaScript when you opt it in with a
client:* directive. Toggle islands below and watch the JS-shipped counter drop.
JS shipped to the browser — live
The zero JS preset is the gold-check state: every framework component renders to static HTML and the page ships 0 KB of JavaScript.
The page — toggle which components are islands
A sample blog-post page, top to bottom. The .astro components are static
HTML — they never ship JS. The framework components (.tsx) let you pick a
client:* directive; pick none and Astro renders them to plain
HTML too.
Before / after — SPA “hydrate everything” vs Astro “hydrate only islands”
SPA model hydrate everything
- One monolithic JS app hydrates the whole page top-down
- A slow component blocks the rest — there is a root that must init first
- The router itself is JS the user must download even to read text
Astro model hydrate only islands
- The page is real HTML — readable before any JS loads
- Each island hydrates independently; one slow island never blocks another
- No framework runtime at all until you explicitly opt in
The client:* directives — reference
From the Astro directives reference: with no client:* directive, a framework
component's HTML is rendered onto the page without JavaScript. Each
directive below opts it into hydration on a different schedule.
| Directive | Priority | When it hydrates | Ships JS? | Use for |
|---|---|---|---|---|
| (none) | — | never — server-rendered HTML only | NO | the default; anything that doesn't need to be interactive |
| client:load | High | immediately on page load | yes | immediately-visible UI that must be interactive ASAP (buy button) |
| client:idle | Medium | requestIdleCallback / load event | yes | lower-priority UI that doesn't need to be instant (show/hide toggle) |
| client:visible | Low | IntersectionObserver — enters viewport | yes | below-the-fold or heavy widgets (image carousel, comments) |
| client:media | Low | a CSS media query matches | yes | sidebar toggle that only exists on small screens |
| client:only | — | client-only — skips server render entirely | yes | components that can't render on the server (depends on window); you MUST pass the framework, e.g. client:only="react" |