Skip to content

Client Directives

In a React app every component hydrates as soon as the bundle loads. Astro gives you fine-grained control: each island declares when it should hydrate via a client:* directive. Without any directive, the component renders to static HTML and ships zero JavaScript.

Hydrates immediately when the page loads. Use this for components that must be interactive right away — a navigation menu, a login form, an above-the-fold widget.

<SearchBar client:load />

This is the direct equivalent of a React component in a Next.js page — it hydrates on load. Use it sparingly; every client:load island adds to the initial JS budget.

Hydrates after the browser fires requestIdleCallback — when the main thread is quiet. Use this for interactive components that are important but not immediately critical.

<NewsletterSignup client:idle />

In a React app there is no equivalent — everything loads eagerly. client:idle is a free performance win for secondary widgets.

Hydrates once the component’s root element enters the viewport (uses IntersectionObserver). Perfect for below-the-fold islands: comments sections, carousels, interactive charts.

<CommentThread client:visible />
<PricingTable client:visible />

A user who never scrolls down never downloads the JS. This is the most impactful directive for long pages.

Hydrates when a CSS media query matches. Use this for islands that only exist in certain layouts — a mobile bottom nav, a desktop sidebar filter.

<MobileMenu client:media="(max-width: 768px)" />
<DesktopFilter client:media="(min-width: 1024px)" />

Desktop users never download the mobile component’s JS; mobile users never download the desktop one.

Skips server-side rendering entirely and renders only in the browser. You must pass the framework name as its value. Use this for components that call browser-only APIs (window, localStorage, WebGL) that would crash during SSR.

<ThreeScene client:only="react" />
<MapWidget client:only="react" />

Without client:only, Astro still runs your React component on the server to generate the initial HTML. If the component calls window or document at the top level, the SSR step will throw. client:only skips that step.

React
// React/Next.js — no directive system.
// All components hydrate on load.
// Lazy loading is manual (React.lazy + Suspense).
import dynamic from 'next/dynamic';
const HeavyChart = dynamic(() => import('./HeavyChart'), {
ssr: false, // skip SSR — closest to client:only
});
export default function Page() {
return (
<>
<SearchBar /> {/* eager hydration */}
<HeavyChart /> {/* ssr:false ~ client:only */}
{/* No built-in idle/visible/media support */}
</>
);
}
Astro
---
import SearchBar from '../components/SearchBar.jsx';
import NewsletterSignup from '../components/NewsletterSignup.jsx';
import CommentThread from '../components/CommentThread.jsx';
import MobileMenu from '../components/MobileMenu.jsx';
import ThreeScene from '../components/ThreeScene.jsx';
---
<!-- Hydrate immediately -->
<SearchBar client:load />
<!-- Hydrate when browser is idle -->
<NewsletterSignup client:idle />
<!-- Hydrate when scrolled into view -->
<CommentThread client:visible />
<!-- Hydrate only on mobile -->
<MobileMenu client:media="(max-width: 768px)" />
<!-- Browser-only, skip SSR -->
<ThreeScene client:only="react" />

The most important thing to remember: a React component without a directive is static HTML. Astro runs it on the server, renders its output to HTML, and ships nothing to the browser.

---
import ProductCard from '../components/ProductCard.jsx';
---
<!-- No client: directive → static HTML, zero JS -->
<ProductCard title="Widget" price={29} />

This is intentional. Astro encourages you to ask: “does this component actually need to be interactive?” If a React component just renders data, it can be static.

Which directive should you use for a comment section that is 1000px below the fold?
What does `client:only="react"` do differently from the other directives?
A React component is imported into an .astro file with no `client:*` directive. What happens?
Which directive is the best fit for a mobile navigation menu that should not load on desktop?