References

Client Directives

Core Patterns

  • Directive Selection: Choose from client:load, client:visible, client:idle, client:media, and client:only based on component priority and interactivity needs
  • Progressive Hydration: Use client:visible for below-fold content and client:idle for non-critical features to minimize initial JavaScript
  • Static Default: Components without a directive render as zero-JavaScript static HTML
  • Performance Strategy: Combine multiple directives to balance initial bundle size against time-to-interactive

Hydration strategies for adding interactivity with minimal JavaScript


When to Read This

  • Adding interactive components to Astro pages
  • Choosing the right hydration strategy
  • Optimizing JavaScript bundle size
  • Understanding client:load, client:visible, client:idle, client:only

Directive Comparison

DirectiveWhen JS LoadsUse CaseBundle Impact
client:loadImmediately on page loadCritical UI (nav, modals)High priority
client:visibleWhen element enters viewportBelow-fold contentLazy loaded
client:idleAfter page becomes idleNon-critical featuresLow priority
client:mediaWhen media query matchesResponsive componentsConditional
client:onlyClient-side only (no SSR)Browser-only APIsCSR only
(no directive)NeverStatic componentsZero JS

client:load (Immediate)

  • Critical UI components
  • Above-the-fold interactivity
  • Components users immediately interact with
---
import Navigation from '../components/Navigation';
import SearchBar from '../components/SearchBar';
---

<!-- Loads immediately -->
<Navigation client:load />
<SearchBar client:load />

Warning: Every client:load adds to initial bundle. Use sparingly.


client:visible (Lazy Load)

  • Below-the-fold components
  • Content that appears on scroll
  • Conditional features
---
import Comments from '../components/Comments';
import Newsletter from '../components/Newsletter';
import RelatedPosts from '../components/RelatedPosts';
---

<article>
  <h1>Blog Post</h1>
  <div>Content...</div>
</article>

<!-- Loads when scrolled into view -->
<Comments client:visible />
<Newsletter client:visible />
<RelatedPosts client:visible />

Benefit: Reduces initial JavaScript, improves Time to Interactive (TTI).


client:idle (Low Priority)

  • Non-critical features
  • Analytics, tracking
  • Features users may not use immediately
---
import ChatWidget from '../components/ChatWidget';
import Analytics from '../components/Analytics';
import SocialShare from '../components/SocialShare';
---

<!-- Loads after browser is idle -->
<ChatWidget client:idle />
<Analytics client:idle />
<SocialShare client:idle />

Benefit: Doesn’t block page load or user interaction.


client:media (Responsive)

  • Mobile vs desktop components
  • Conditional features based on screen size
---
import MobileNav from '../components/MobileNav';
import DesktopNav from '../components/DesktopNav';
---

<!-- Loads only on mobile -->
<MobileNav client:media="(max-width: 768px)" />

<!-- Loads only on desktop -->
<DesktopNav client:media="(min-width: 769px)" />

client:only (Client-Side Rendering)

  • Components using browser-only APIs (window, document)
  • Third-party widgets (Google Maps, Stripe)
  • Libraries that break during SSR
---
import MapWidget from '../components/MapWidget';
import StripeCheckout from '../components/StripeCheckout';
---

<!-- No SSR, only runs in browser -->
<MapWidget client:only="react" />
<StripeCheckout client:only="react" />

Drawback: No SEO, no server rendering, potential layout shift.


No Directive (Static)

  • Static content (headers, footers, text)
  • Components without interactivity
  • Pure presentation components
---
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
---

<!-- No JavaScript, pure HTML -->
<Header />
<main>
  <h1>Content</h1>
</main>
<Footer />

Benefit: Zero JavaScript, fastest performance.


Performance Impact

Bundle Size Comparison

---
import Counter from '../components/Counter'; // 5KB component
---

<!-- Option 1: 5KB immediately -->
<Counter client:load />

<!-- Option 2: 5KB when visible (lazy) -->
<Counter client:visible />

<!-- Option 3: 5KB when idle -->
<Counter client:idle />

<!-- Option 4: 0KB (no JS) -->
<!-- Can't use Counter without directive if it needs state -->

Multiple Components

<!-- ❌ BAD: 50KB JavaScript immediately -->
<ComponentA client:load /> <!-- 10KB -->
<ComponentB client:load /> <!-- 10KB -->
<ComponentC client:load /> <!-- 10KB -->
<ComponentD client:load /> <!-- 10KB -->
<ComponentE client:load /> <!-- 10KB -->

<!-- ✅ GOOD: 10KB immediately, 40KB lazy loaded -->
<ComponentA client:load /> <!-- Critical: 10KB -->
<ComponentB client:visible /> <!-- Below fold: 10KB -->
<ComponentC client:visible /> <!-- Below fold: 10KB -->
<ComponentD client:idle /> <!-- Non-critical: 10KB -->
<ComponentE client:idle /> <!-- Non-critical: 10KB -->

Decision Tree

Is component interactive?

  • No → No directive (static HTML)
  • Yes → Continue

Does it use browser-only APIs (window, document)?

  • Yes → client:only
  • No → Continue

Is it critical for initial UX (navigation, search)?

  • Yes → client:load
  • No → Continue

Is it visible on page load (above fold)?

  • No → client:visible (lazy load when scrolled into view)
  • Yes → Continue

Is it essential for user’s first interaction?

  • No → client:idle (load after page is interactive)
  • Yes → client:load

Is it responsive (mobile/desktop specific)?

  • Yes → client:media="(condition)"

Common Patterns

Progressive Enhancement

---
import Accordion from '../components/Accordion';
---

<!-- Works without JS (CSS-only), enhanced with JS -->
<Accordion client:visible />

Critical + Non-Critical

<!-- Critical: Nav must work immediately -->
<Navigation client:load />

<!-- Non-critical: Chat can load later -->
<ChatWidget client:idle />

Conditional Rendering

---
import AdminPanel from '../components/AdminPanel';
const user = Astro.locals.user;
---

{user?.role === 'admin' && (
  <AdminPanel client:load />
)}

References