Using Astro's Islands Architecture to Load JavaScript Only When Needed
Learn how Astro’s Islands Architecture lets you load JavaScript only when needed, using client:load, client:visible, and client:only directives to keep content‑heavy sites fast.
31 May 2026, 12:26 UTC

The JavaScript Overhead on Content‑Heavy Sites
Many modern frameworks treat a page as a single JavaScript application. Even when only a small widget—like a site‑wide search or a mobile menu—needs interactivity, the browser still downloads, parses, and executes a large bundle before the page feels responsive. This extra work inflates Time to Interactive and can hurt Core Web Vitals, especially on mobile connections.
Astro’s Islands Architecture flips that model. By default, Astro renders everything to static HTML. You then explicitly mark the components that need interactivity as “islands” and choose when they should be hydrated. The result is that you ship only the JavaScript required for the parts of the page the user actually interacts with.
Choosing a Hydration Strategy
Astro provides three client:* directives that control when a component’s JavaScript is sent to the browser:
- client:load – Hydrates the component immediately as the page loads. Use it for UI that must be ready right away, such as a primary navigation toggle or a cookie consent banner.
- client:visible – Defers hydration until the component enters the viewport, using the Intersection Observer API. Ideal for heavy widgets placed further down the page, like a product carousel or a comment thread.
- client:only – Skips server‑side rendering entirely. The component renders only in the browser, which is necessary when it depends on browser‑only APIs (
window,localStorage, etc.). Note that the initial HTML contains no markup for this component, so crawlers that do not execute JavaScript will not see its content.
Picking the wrong directive can either waste bandwidth (over‑using client:load) or break functionality (using client:only for SEO‑critical content).
Worked Example: A Product Page with Mixed Frameworks
Imagine a product detail page where:
- The site header is a React component that needs to be interactive immediately.
- The product description is plain Markdown (no interactivity).
- A “Related Products” carousel at the bottom is built with Svelte and should only load when the user scrolls near it.
First, make sure you have an Astro project. If you don’t, create one:
npm create astro@latest my-product-site
cd my-product-site
Then add the React and Svelte integrators:
# Install the framework adapters
npm install @astrojs/react @astrojs/svelte
# Register them with Astro
astro add @astrojs/react
astro add @astrojs/svelte
No special permissions are required; these commands run in your local development environment.
Now the page component:
-- src/pages/product/[id].astro --
---
import Header from '../components/Header.jsx';
import ProductDescription from '../components/ProductDescription.astro';
import RelatedProducts from '../components/RelatedProducts.svelte';
---
<!-- Hydrates immediately for navigation -->
<Header client:load />
<!-- Zero JavaScript shipped to the client -->
<ProductDescription />
<!-- The carousel loads its JS only when it scrolls into view -->
<RelatedProducts client:visible />
The client:visible directive relies on the component’s natural position in the document. You do not need artificial spacers; simply place the component where it appears in the flow, and the Intersection Observer will trigger the fetch as the user scrolls.
Trade‑off: State Management Between Islands
Because each island is rendered with its own framework runtime, they do not share a common React, Vue, or Svelte context. Passing complex data between a React header and a Svelte carousel, for example, cannot be done through props or a single framework‑specific store.
A practical, framework‑agnostic solution is to use a tiny state library like Nano Stores. You create a store in a plain JavaScript module, import it into each island, and subscribe to updates. Since the store is just a plain object with getter/setter functions, it works regardless of the UI library that built the island.
Verifying That Islands Behave as Expected
You can confirm the hydration strategy without any special tooling:
- View page source – Right‑click the page and choose “View Page Source.” Components without a
client:*directive should appear as plain HTML. A component markedclient:only will have no markup in the source. - Network tab in DevTools – Open Chrome DevTools → Network, filter by JS, and reload the page. Scroll down until a
client:visible component enters the viewport; you should see a new JavaScript request fire at that moment. - Lighthouse audit – Run a performance audit. A site that correctly uses islands will typically show a lower Total Blocking Time (TBT) and a faster Speed Index compared to the same UI built as a traditional SPA.
If you notice that a client:visible component’s bundle loads on page load, double‑check that the directive is spelled correctly and that the component is not hidden by CSS that prevents it from ever entering the viewport.
Closing Thoughts
Astro’s Islands Architecture lets you treat interactivity as an opt‑in feature rather than the default. By matching each directive to the component’s role—client:load for critical UI, client:visible for down‑page widgets, and client:only for browser‑only code—you keep the initial payload small and maintain good Core Web Vitals. Remember to handle cross‑island state with a lightweight, framework‑agnostic store, and verify your setup with the simple checks above.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.