Astro Islands Architecture: When and How to Hydrate Your Components
A practical guide to Astro's partial hydration model—understanding client directives, choosing the right strategy, and keeping your bundles lean while maintaining interactivity.
When I migrated my portfolio from a traditional React SPA to Astro, my JavaScript bundle dropped from 287KB to 43KB. The secret? Astro’s Islands Architecture—shipping HTML by default and only hydrating components that actually need interactivity.
But here’s the thing: knowing when to hydrate is just as important as knowing how.
What Are Islands?
Think of your page as an ocean of static HTML with interactive “islands” floating in it. Each island is an independent, self-contained component that can:
- Load its JavaScript independently
- Hydrate on its own schedule
- Function without affecting other islands
---
// Most of this page is static HTML
import Navbar from '../components/Navbar';
import Hero from '../components/Hero';
import Footer from '../components/Footer';
---
<html>
<body>
<!-- Interactive island: loads immediately -->
<Navbar client:load />
<!-- Interactive island: loads when visible -->
<Hero client:visible />
<!-- Static: zero JavaScript -->
<main>
<h1>About Me</h1>
<p>This is just HTML. No JS needed.</p>
</main>
<!-- Interactive island: loads when visible -->
<Footer client:visible />
</body>
</html>
The Client Directives
Astro provides several directives to control hydration:
client:load
Hydrates immediately when the page loads.
Use for:
- Navigation menus (users interact immediately)
- Above-the-fold interactive elements
- Critical functionality
<Navbar client:load />
client:visible
Hydrates when the component enters the viewport.
Use for:
- Below-the-fold content
- Comment sections
- Footer interactions
- Image galleries
<ContactForm client:visible />
client:idle
Hydrates when the browser is idle (using requestIdleCallback).
Use for:
- Analytics widgets
- Chat widgets
- Non-critical enhancements
<AnalyticsDashboard client:idle />
client:media
Hydrates only when a media query matches.
Use for:
- Mobile-only navigation
- Desktop-only features
- Responsive interactive elements
<MobileMenu client:media="(max-width: 768px)" />
client:only
Skips server rendering entirely. Renders only on client.
Use for:
- Components that can’t SSR (accessing
window, etc.) - Third-party widgets
- Browser-specific features
<ThreeJSScene client:only="react" />
My Decision Framework
Here’s the flowchart I use for every component:
Does the component need JavaScript?
├── No → Don't use any client directive (static)
└── Yes → Is it above the fold?
├── Yes → Is it immediately interactive?
│ ├── Yes → client:load
│ └── No → client:idle
└── No → client:visible
Real Examples from This Site
| Component | Directive | Reasoning |
|---|---|---|
| Navbar | client:load | Users click nav links immediately |
| Hero | client:load | Above fold, has animations |
| About | client:visible | Below fold, scroll-triggered |
| Projects | client:visible | Below fold, hover effects |
| Contact Form | client:visible | Below fold, form validation |
| 3D Background | client:load | Above fold, visual impact |
Performance Impact
Let’s look at the real numbers from my portfolio:
Before (React SPA)
Total JS: 287KB
First Contentful Paint: 2.1s
Time to Interactive: 3.8s
Lighthouse Performance: 67
After (Astro Islands)
Total JS: 43KB
First Contentful Paint: 0.8s
Time to Interactive: 1.2s
Lighthouse Performance: 96
That’s an 85% reduction in JavaScript and a 68% improvement in Time to Interactive.
Common Patterns
Pattern 1: Progressive Enhancement
Start with a working HTML form, enhance with JavaScript:
---
import ContactForm from '../components/ContactForm';
---
<!-- Works without JS (basic HTML form) -->
<form action="/api/contact" method="POST">
<input name="email" type="email" required />
<textarea name="message" required></textarea>
<button type="submit">Send</button>
</form>
<!-- Enhanced with client-side validation when visible -->
<ContactForm client:visible />
Pattern 2: Hybrid Static/Interactive Lists
---
import { getProjects } from '../data/projects';
import ProjectCard from '../components/ProjectCard';
const projects = await getProjects();
---
<div class="project-grid">
{projects.map(project => (
<!-- Each card hydrates independently when visible -->
<ProjectCard
project={project}
client:visible
/>
))}
</div>
Pattern 3: Lazy Loading Heavy Components
---
// Don't import heavy 3D library at build time
---
<!-- Only loads Three.js when component is visible -->
<div id="3d-container">
<ThreeScene client:visible client:only="react" />
</div>
Debugging Hydration
Astro Dev Tools show you exactly what’s hydrating and when:
npm run dev
# Check the Astro overlay in your browser
You can also add visual indicators in development:
// In your component
useEffect(() => {
console.log('🏝️ Component hydrated:', componentName);
}, []);
Anti-Patterns to Avoid
❌ Over-hydrating
<!-- Bad: Everything loads immediately -->
<StaticContent client:load />
<AnotherStaticThing client:load />
If it doesn’t need JavaScript, don’t add a client directive.
❌ Under-hydrating
<!-- Bad: Critical nav hidden until scroll -->
<Navbar client:visible />
Users need to navigate before scrolling.
❌ Using client:only When SSR Works
<!-- Bad: Unnecessary client:only -->
<SimpleButton client:only="react" />
<!-- Good: Server-render, then hydrate -->
<SimpleButton client:load />
When Islands Aren’t Right
Islands architecture isn’t for every project:
- Highly interactive apps (dashboards, editors): Consider full React/Vue
- Real-time updates: WebSocket-heavy apps may need full hydration
- Complex shared state: Multiple islands needing synchronized state
For content sites, marketing pages, and portfolios like this one? Islands are perfect.
Key Takeaways
- Default to static — No directive means zero JavaScript
- Match directive to behavior —
client:loadfor critical,client:visiblefor below-fold - Measure the impact — Check your bundle size and TTI
- Think in islands — Each interactive piece is independent
- Progressive enhance — Make it work without JS first
Want to learn more about building performant Astro sites? Check out my other posts on web performance and design systems.