Reducing TBT with Astro's Islands Architecture
Learn how Astro's Islands Architecture eliminates unnecessary JavaScript by using partial hydration and client directives to improve page load speed and TBT.
31 Jul 2026, 14:16 UTC

The JavaScript Bloat Problem
Modern web development often forces a binary choice: build a static site that feels dead, or build a Single Page Application (SPA) that ships a massive JavaScript bundle. When you use an SPA framework, the browser must download, parse, and execute the entire framework runtime before the page becomes interactive. This leads to high Total Blocking Time (TBT), where the main thread is frozen, and users cannot click buttons or scroll smoothly.
Astro solves this by treating JavaScript as an opt-in enhancement rather than a requirement. Through Islands Architecture, Astro renders your entire page to static HTML on the server and only "hydrates" (attaches JavaScript event listeners to) the specific components that actually need interactivity.
How Partial Hydration Works
In a traditional React or Vue app, the entire DOM tree is managed by JavaScript. In Astro, the default state of any component is static HTML. If you drop a React component into an Astro page without any special instructions, Astro renders it to HTML during the build process and ships zero JavaScript to the browser.
To make a component interactive, you use client directives. These tell Astro exactly when to send the JavaScript bundle to the client. This process is called partial hydration because only "islands" of interactivity are brought to life, while the rest of the page remains lightweight HTML.
Choosing the Right Directive
client:load: Hydrates the component immediately on page load. Use this for critical UI like navigation menus.client:idle: Hydrates once the browser reaches an idle state (usingrequestIdleCallback). Use this for lower-priority elements like a newsletter signup.client:visible: Hydrates only when the component enters the viewport using the Intersection Observer API. This is ideal for heavy components located halfway down a page.client:only: Skips server-rendering entirely and renders only on the client. Use this for components that rely on browser-only APIs likelocalStorage.
Worked Example: Selective Interactivity
Consider a product page with a static description, a dynamic image gallery, and a "Related Products" carousel at the bottom. Loading the carousel JS on page load is wasteful if the user never scrolls down.
-- ProductPage.astro --
---
import Header from '../components/Header.jsx';
import ProductDescription from '../components/ProductDescription.jsx';
import ImageGallery from '../components/ImageGallery.jsx';
import RelatedProducts from '../components/RelatedProducts.jsx';
---
<Header />
<ProductDescription />
<ImageGallery client:load />
<div class="spacer" style="height: 100vh;"></div>
<RelatedProducts client:visible />
Verification Steps
To verify that the Islands Architecture is working as intended, follow these steps in a browser (Chrome/Edge):
- Open DevTools > Network and filter by
JS. - Refresh the page. You should see the bundle for
ImageGalleryload immediately. - Observe that no bundle for
RelatedProductshas been requested yet. - Scroll down to the
RelatedProductscomponent. You should see a new JS request trigger the moment the component enters the viewport.
The Trade-off: Island Isolation
The primary limitation of this architecture is that islands are isolated by default. Because they are hydrated independently, a React island cannot directly call a function inside a Vue island or share a local state variable with a Svelte island.
If you need to synchronize state between two distant islands (e.g., a "Add to Cart" button in one island updating a "Cart Counter" in another), you cannot use standard framework props. Instead, you must use a shared state manager that exists outside the component lifecycle, such as nanostores, or utilize standard browser CustomEvents to communicate via the DOM.
Actionable Summary
To optimize your Astro project, audit your components and remove client:load from any element that is not visible above the fold. Move non-critical interactivity to client:visible to reduce the initial JS payload and improve your Core Web Vitals.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.