Skip to content

Client Directives

A framework component imported into an .astro file renders to static HTML — no interactivity — until you add a client directive. The directive tells Astro two things: hydrate this component on the client, and when to do it.

---
import Counter from '../components/Counter.jsx';
---
<Counter /> <!-- static HTML, no JS ships -->
<Counter client:load /> <!-- an island: hydrates immediately -->

The “when” is the whole game. Hydrating earlier makes a component interactive sooner but costs upfront JavaScript and main-thread time; hydrating later keeps the page lean but delays interactivity. Each directive is a different point on that trade-off.

  • client:load — hydrate immediately, as soon as the page loads. Use for above-the-fold controls that must work the instant the page is usable: a header search, a critical toggle. The most eager, most expensive option.
  • client:idle — hydrate when the browser is idle (via requestIdleCallback), after the main work of loading is done. Good for interactivity that’s important but not instant — it waits for a quiet moment.
  • client:visible — hydrate when the component scrolls into view (via IntersectionObserver). The best default for anything below the fold: a comments widget, a footer newsletter form, a carousel far down the page. Its JavaScript never loads if the user never scrolls there.
  • client:media — hydrate only when a CSS media query matches, e.g. client:media="(max-width: 50em)". Use for interactivity that only exists at some breakpoints — a mobile-only hamburger menu that’s pointless on desktop.
  • client:only="react"skip server rendering entirely and render only on the client. You must name the framework ("react", "vue", "svelte", …) because Astro won’t render it on the server to detect it. Use for components that can’t render on the server at all — ones that touch window, localStorage, or a browser-only library on first render.
flowchart LR
  start["page loads"] --> load["client:load — hydrate now"]
  load --> idle["client:idle — when browser is idle"]
  idle --> visible["client:visible — when scrolled into view"]
  visible --> media["client:media — when a breakpoint matches"]
  only["client:only — skip server render, client only"]
When each directive hydrates, along the page's timeline

A simple decision path covers most cases:

  • Must it work the moment the page loads, above the fold? → client:load.
  • Is it below the fold, or not needed instantly? → client:visible (the safest lean default).
  • Important but can wait for a quiet moment? → client:idle.
  • Only relevant at a certain screen size? → client:media.
  • Can it not render on the server at all? → client:only="react".

The instinct to reach for client:load on everything is exactly what islands exist to prevent. Ship the least JavaScript that delivers the interactivity, and let most of your islands be client:visible.

Which directive is the best lean default for an interactive widget below the fold?
Why must you write client:only="react" with the framework name?
What is the trade-off of hydrating a component earlier (e.g. client:load vs client:visible)?
When would you reach for client:media?