Hydrate Astro Islands Only When They Enter the Viewport
Use Astro client directives to load framework components only when needed, keeping pages zero-JS until interactivity is required.
04 Jun 2026, 10:29 UTC

The problem: interactivity without the JavaScript tax
You build an Astro site because it ships almost no client-side JavaScript by default. Then you add a comment thread, a data chart, or a dropdown menu built in React or Vue. Suddenly the page includes a framework runtime, and the browser parses and executes it before the user has scrolled anywhere near the widget.
Astro calls an isolated interactive component an island. By default, Astro renders every component to static HTML and sends zero JavaScript. To make an island interactive, you add a client directive such as client:load, client:idle, client:visible, client:only, or client:media. The directive tells Astro when—and whether—to hydrate the component in the browser.
Choosing the right directive
Not every island needs the same loading strategy. Use this comparison to match the directive to the widget's position and priority.
| Directive | When it hydrates | Best for |
|---|---|---|
| client:load | Immediately, alongside the page | Above-the-fold interactivity that must work right away |
| client:idle | When the browser's main thread is free | Non-critical widgets below the main content |
| client:visible | When the component enters the viewport, usually via Intersection Observer | Charts, comments, or embeds further down the page |
| client:only | Immediately, with no server-rendered HTML | Components that only make sense in the browser |
| client:media | When a media query matches | Responsive widgets that only matter on certain screen sizes |
client:visible is often the best default for content below the fold. The page stays zero-JS until the user scrolls the widget into view, at which point Astro loads the component's JavaScript and hydrates it.
Worked example: a chart that loads on scroll
Assume you have an Astro project using Astro 4.x or later with a React integration installed. Create a chart component in src/components/LazyChart.jsx:
import { useState } from 'react';
export default function LazyChart() {
const [data, setData] = useState(null);
if (typeof window !== 'undefined' && !data) {
setData([1, 3, 2, 5]);
}
return (
{data ? Chart values: {data.join(', ')}
: Loading chart...
}
);
}
Import and use it on a page in src/pages/index.astro with client:visible:
---
import LazyChart from '../components/LazyChart.jsx';
---
Blog post
Long article content...
Scroll down to see the chart.
Run the production build from the project root:
npm run build
No special permissions are required. The command compiles pages into dist/. After the build, inspect dist/pages/index.html. The markup should contain a placeholder for the island but no inline framework runtime script for LazyChart.
To verify the loading behavior, open the built site locally with npm run preview and open Chrome DevTools. Go to the Network panel, filter by JS, reload the page, and confirm the chart's JavaScript chunk is not requested. Scroll the chart into view; the request should fire only after the element intersects the viewport.
You can also open the Coverage tab in DevTools, reload the page, and compare unused JavaScript against a version of the page that uses client:load. The client:visible version should ship less JavaScript up front.
Limitations and what to check after upgrading
- client:visible depends on Intersection Observer. In older browsers, hydration may not start unless you include a polyfill.
- If the element never enters the viewport, the component never hydrates and stays non-interactive.
- client:only omits server-rendered HTML, so crawlers that do not execute JavaScript will not see the component's content.
- Adding many client:load islands reintroduces framework runtimes and can erase Astro's zero-JS advantage.
Directive names and hydration semantics can change between Astro major versions. After upgrading Astro, rebuild and re-check the Network panel to confirm islands still load at the expected times.
Closing
Astro's client directives let you keep most of a page static while isolating interactivity to the widgets that need it. Start with client:visible for below-the-fold components, verify with the Network panel, and reserve client:load for elements that must work immediately.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.