Astro Client Directives: Picking the Right Hydration Trigger for Each Island
Astro ships zero JavaScript by default; client directives decide when each interactive island hydrates. Learn what client:load, idle, visible, media and only actually do, with a worked chart example and the mistakes that quietly recreate SPA-sized bundles.
28 May 2026, 09:36 UTC

Astro renders every component to static HTML at build time and ships zero JavaScript by default. The moment you add an interactive framework component — a React counter, a Svelte chart — you decide when its JavaScript loads by attaching a client directive. Choosing the laziest directive that still meets the user experience is the single biggest lever you have over an Astro page's JS payload.
The five directives and what they actually do
An "island" is a framework component Astro server-renders to HTML, then hydrates independently in the browser. The directive controls the trigger:
client:load— hydrate immediately on page load. Use for above-the-fold interactivity the user needs right away (nav menus, search boxes).client:idle— hydrate when the browser is idle, viarequestIdleCallback. Good for low-priority UI that should be ready soon but not block initial load.client:visible— hydrate when the island scrolls into the viewport, using anIntersectionObserver. Best for anything below the fold.client:media— hydrate when a CSS media query matches, e.g.client:media="(max-width: 768px)"for a mobile-only menu.client:only="react"— skip server rendering entirely; render only in the browser. Reserved for components that depend on browser APIs likewindowor canvas.
Worked example: deferring a heavy chart
Say a marketing page has a large charting component near the bottom. Shipping it with client:load forces every visitor to download and execute the chart library even if they never scroll. Instead:
---
import Chart from '../components/RevenueChart.jsx';
---
<html>
<body>
<h1>Annual report</h1>
<!-- long static content... -->
<Chart client:visible data={reportData} />
</body>
</html>
The chart's HTML renders at build time, so users scrolling down see content immediately. The JavaScript only fetches and hydrates when the island enters the viewport. Everything else on the page ships no JavaScript at all. You can confirm this in DevTools: view the page source to see the server-rendered markup, then watch the Network tab while scrolling — the chart's bundle appears only on intersection.
Limits and common mistakes
Islands don't share state
Each island hydrates independently. Two React islands cannot share state through React context, because they are separate roots. If a cart icon and a product grid must stay in sync, use an external store such as nanostores, or dispatch custom DOM events between them.
Props must be serializable
Props cross the server-to-client boundary as serialized data. Strings, numbers, plain objects and arrays survive; functions, class instances and Date objects do not. Pass an ISO string and reconstruct the Date inside the component.
client:only causes layout shift
A client:only component renders an empty placeholder on the server, so the browser shows nothing until hydration. That means a content flash and possible layout shift, and nothing for crawlers or no-JS users. Reserve it for genuinely browser-only widgets, and give the placeholder explicit dimensions to reduce shift.
Hydration mismatches
If server-rendered markup depends on browser-only values — Date.now(), window.innerWidth, random IDs — the client render won't match the server HTML and hydration can warn or break. Render deterministic content on the server and apply browser-dependent values after mount.
Overusing client:load
Sprinkling client:load on a dozen components recreates the SPA payload Astro exists to avoid. Audit each island: if it's below the fold, client:visible; if it's non-urgent, client:idle. Keep client:load for what the user touches in the first seconds.
Verifying your choices
Build the page and inspect three things: the HTML source (non-hydrated components should appear as plain markup with no associated script), the Network tab while scrolling (client:visible islands load only on intersection), and your framework's devtools to confirm when each component mounts. Note that client:idle timing depends on browser support for requestIdleCallback, so test on your target browsers rather than assuming a fixed delay.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.