Astro renders every component to HTML and strips out its client-side JavaScript by default. You then explicitly mark the components that need to run in the browser, and only those get hydrated. That inversion — opt in to JavaScript rather than opt out — is the whole idea, and it is why an Astro page often ships zero kilobytes of framework code.
The contrast is with the single-page application model, where the entire page is a JavaScript application that renders itself, and hydration is all-or-nothing across the whole tree. For a content site — a blog, documentation, marketing pages, a shop’s catalogue — most of the page never changes after it loads, and shipping a framework runtime to render text that could have been HTML is the cost that islands removes.
Table of contents
- What an island actually is
- The client directives
- Server islands
- Structuring components so islands stay small
- When Astro is the wrong choice
- Measuring whether it worked
- How this fits the rest of the stack
- FAQ
What an island actually is
An island is an interactive component on an otherwise static page. Astro components themselves are always static — they run at build time and produce HTML. Islands are components from a UI framework (React, Preact, Svelte, Vue, Solid) that you have marked for hydration.
---
import Header from '../components/Header.astro';
import SearchBox from '../components/SearchBox.jsx';
import Newsletter from '../components/Newsletter.jsx';
---
<Header />
<SearchBox client:load />
<article><slot /></article>
<Newsletter client:visible />
Header ships as HTML with no JavaScript at all. SearchBox hydrates as soon as the page loads. Newsletter only hydrates when it scrolls into view. The article between them is static markup.
Critically, each island is independent. They hydrate separately, in parallel, and a slow one does not block a fast one. There is no single root component whose hydration gates the entire page, which is the structural difference from an SPA.
You can also mix frameworks on one page — a React island beside a Svelte island — because each carries its own small runtime rather than sharing a global one. Useful during a migration; not something to do casually.
The client directives
client:load— hydrate immediately on page load. For anything above the fold that must be interactive at once: a header search, a critical form.client:idle— hydrate when the browser is idle. For things that should work soon but not urgently.client:visible— hydrate when it scrolls into the viewport. The best default for anything below the fold, and frequently the one that saves the most JavaScript, because most visitors never reach the bottom.client:media={"(max-width: 768px)"}— hydrate only when a media query matches. A mobile-only drawer menu ships no JavaScript to desktop visitors at all.client:only="react"— skip server rendering entirely and render only in the browser. For components that touch browser-only APIs and cannot render on the server.
client:visible deserves to be the reflex. On a typical page, most interactive components sit below the fold and a large share of visitors never scroll to them. Deferring hydration until they are actually seen is close to free.
client:only is the one to use sparingly. It means no server-rendered HTML, so there is nothing to see until JavaScript runs — a blank space during load, and nothing for a crawler. Use it when a library genuinely cannot render server-side, not as a way past a hydration warning.
A component with no directive at all ships as static HTML. It is not broken; it just has no event handlers. This confuses people once — a React button that renders and does nothing — and then never again.
Server islands
The newer half of the model, and it solves a different problem: a page that is mostly static but has one piece of personalised or slow content.
---
import Avatar from '../components/Avatar.astro';
import Recommendations from '../components/Recommendations.astro';
---
<header>
<Avatar server:defer>
<GenericAvatar slot="fallback" />
</Avatar>
</header>
<main>
<article>...mostly static content...</article>
<Recommendations server:defer>
<p slot="fallback">Loading recommendations...</p>
</Recommendations>
</main>
</main>
server:defer turns a component into a server island: the page ships immediately with the fallback content in place, and the deferred component is server-rendered separately and swapped in when ready.
This resolves the problem that used to force an entire page out of static rendering. Previously, one personalised greeting meant the whole page had to render per request. Now the page is cached and static while the greeting arrives separately.
The slot="fallback" content is what the user sees first, so make it the right shape — a skeleton matching the final dimensions rather than nothing, so the layout does not jump when the real content lands.
Structuring components so islands stay small
The most common mistake is marking a large component as an island when only a small part of it is interactive.
Consider a product card with an image, a title, a description, and an add-to-cart button. Marking the whole card client:load ships the entire card’s rendering logic to the browser. Only the button needs to be interactive.
<div class="card">
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>{product.description}</p>
<AddToCart client:visible productId={product.id} />
</div>
Now the card is static HTML and only the button hydrates. On a page with thirty products, that is thirty small islands instead of thirty full component trees — a very large difference in shipped JavaScript.
The general rule: push the client: directive as deep into the tree as it will go. Every wrapper you avoid hydrating is code that never crosses the network.
One consequence to plan around: islands are isolated, so they do not share React context or a component-level store with each other. Cross-island state needs something outside the framework — nanostores is the common choice, or plain browser events. Designing for a handful of independent islands rather than one interconnected app is the mental shift that Astro asks for.
When Astro is the wrong choice
Worth saying, because the performance numbers make it tempting for everything.
- A genuine application — a dashboard, an editor, an admin panel. If most of the page is interactive and stateful, islands add ceremony without benefit. Use a framework built for that.
- Heavy shared client state across many components. The isolation that makes islands cheap makes this awkward.
- A team deeply invested in one framework’s ecosystem — routing, data fetching, form libraries. Astro can host the components but not the whole ecosystem’s conventions.
- Highly dynamic per-request content everywhere. Server islands help, but if nothing on the page is cacheable the static-first model is not buying you much.
Where it is clearly right: blogs, documentation, marketing sites, landing pages, portfolios, catalogues — anything content-first with pockets of interactivity. That is a very large share of the web, and it is the share that has been paying an SPA tax it never needed.
The honest framing is that islands are an architecture, not a framework feature. Astro popularised the pattern and several other frameworks now offer versions of it. The idea — static by default, interactive by exception — outlives any particular tool.
Measuring whether it worked
The metric islands most directly improve is Total Blocking Time, because less JavaScript means less main-thread work parsing and executing it. Largest Contentful Paint improves too when the content was previously waiting on hydration.
Check the network tab for the JavaScript your page actually ships. On a well-structured Astro content page it should be small enough to be surprising — and if it is not, the usual cause is one component marked client:load that wraps far more than it needs to.
Astro’s build output lists the client bundles it produced per page, which is the fastest way to find the island that is larger than you thought. It is worth reading after any change that adds a directive.
How this fits the rest of the stack
Islands cut what a page ships; the hosting decides how fast the first byte arrives, which is the part no front-end architecture can fix. Astro’s static output is a directory of files, which is exactly what static hosting on RunxBuild builds from a repository — custom domains, headers, redirects, rewrites, SPA fallback, and 120GB of bandwidth included before overage applies. Static sites on RunxBuild covers the build and routing settings, and if a server-island setup means you also need a backend service and a database, the RunxBuild hosting calculator shows the static site, the service, the database, and bandwidth as separate figures rather than one bundled number.
Useful related references:
FAQ
What is Astro islands architecture?
A pattern where the page renders to static HTML by default and only components you explicitly mark are hydrated with JavaScript in the browser. Each island hydrates independently, so there is no single root component whose hydration gates the whole page.
What is the difference between client:load and client:visible?
client:load hydrates the component as soon as the page loads. client:visible waits until it scrolls into the viewport. For anything below the fold, client:visible is usually the better default, since many visitors never scroll far enough to need it.
What is a server island in Astro?
A component marked server:defer that is server-rendered separately from the rest of the page. The page ships immediately with fallback content and the deferred part is swapped in when ready — so one piece of personalised content no longer forces the entire page out of static rendering.
Why does my React component not work in Astro?
It probably has no client directive. Without one, Astro renders the component to HTML and strips its JavaScript, so it displays correctly but has no event handlers. Add client:load or client:visible depending on where it sits on the page.
When should I not use Astro?
For genuine applications — dashboards, editors, admin panels — where most of the page is interactive and stateful. Islands are isolated and do not share framework context, so heavy cross-component state becomes awkward. Astro suits content-first sites with pockets of interactivity.