git clone https://github.com/Intense-Visions/harness-engineering
T=$(mktemp -d) && git clone --depth=1 https://github.com/Intense-Visions/harness-engineering "$T" && mkdir -p ~/.claude/skills && cp -r "$T/agents/skills/claude-code/astro-islands-architecture" ~/.claude/skills/intense-visions-harness-engineering-astro-islands-architecture && rm -rf "$T"
agents/skills/claude-code/astro-islands-architecture/SKILL.mdAstro Islands Architecture
Ship zero JavaScript by default — hydrate only the interactive components that need it, exactly when they need it.
When to Use
- You are building a page that mixes static content with interactive UI components
- You need to choose the right
directive for a framework component (React, Vue, Svelte, Solid, Preact)client:* - A page is shipping more JavaScript than necessary and you want to audit hydration
- You are deciding whether a component should be a static
component or a hydrated island.astro - You want to defer expensive component hydration until the user actually needs it
Instructions
-
Default to
components for all non-interactive UI. They render to pure HTML with zero JS shipped to the browser..astro -
Use framework components (React, Vue, Svelte, etc.) only when client-side interactivity is required (event handlers, stateful UI, browser APIs).
-
Apply a
directive to every framework component that must hydrate. Without a directive, the component renders as static HTML only.client:* -
Choose the directive that matches when the component becomes interactive:
— hydrate immediately on page load. Use for above-the-fold critical UI (navigation menus, login forms).client:load
— hydrate after the browser'sclient:idle
. Use for non-critical interactive widgets.requestIdleCallback
— hydrate when the component scrolls into the viewport (Intersection Observer). Use for below-the-fold content, carousels, comment sections.client:visible
— hydrate only when a CSS media query matches. Use for mobile-only components.client:media="(max-width: 768px)"
— skip SSR entirely, render and hydrate on the client only. Use for components that depend on browser-only APIs (client:only="react"
,window
, WebGL).localStorage
-
Never use
as a default for all interactive components. Preferclient:load
orclient:idle
whenever the component is not immediately visible.client:visible -
Pass data into islands via props. Islands are isolated — they do not share state automatically.
--- // src/pages/index.astro import StaticHeader from '../components/StaticHeader.astro'; import Counter from '../components/Counter.jsx'; import HeavyChart from '../components/HeavyChart.jsx'; import MobileMenu from '../components/MobileMenu.jsx'; --- <!-- Zero JS: pure static HTML --> <StaticHeader /> <!-- Critical interactive: hydrate immediately --> <Counter client:load initialCount={0} /> <!-- Below the fold: hydrate when visible --> <HeavyChart client:visible data={chartData} /> <!-- Mobile only: skip on desktop entirely --> <MobileMenu client:media="(max-width: 768px)" />
-
Audit your islands by running the build and inspecting the
output. Eachdist/
directive produces a separate JS chunk. Large chunks indicate over-hydration.client:* -
Use
from the View Transitions API to keep an island alive across page navigations — avoiding a re-hydration cost.transition:persist
Details
Islands Architecture was coined by Katie Sylor-Miller and popularized by Jason Miller. The central idea: treat the page as a sea of static HTML with interactive "islands" embedded within it. Each island is an independently hydrated component — its JavaScript is fetched and executed only for that component, not for the whole page.
Why this matters: A traditional SPA ships a full JavaScript bundle and hydrates the entire page. Astro's island approach means a content-heavy page (blog post, marketing landing page, documentation) ships zero JavaScript unless you explicitly add
client:* to a component. This dramatically improves Time to Interactive (TTI) and Largest Contentful Paint (LCP).
How hydration works under the hood:
Astro serializes the component's props into the HTML at build time. When the browser loads the page, Astro's tiny runtime reads the directive, waits for the right moment (load, idle, visible, media match), then dynamically imports the component's framework bundle and hydrates it with the serialized props. Each island is fully independent.
Trade-offs:
- Islands cannot directly share React state with each other. Cross-island state requires nanostores or a shared URL/storage mechanism.
components are invisible to SEO crawlers and do not SSR. Use with care for content that should be indexed.client:only
adds an Intersection Observer per island — negligible cost, but be aware on pages with hundreds of islands.client:visible- Passing non-serializable props (class instances, functions, symbols) to islands will fail. Props must be plain JSON-serializable values.
Island communication patterns:
- Use nanostores (
,@nanostores/react
, etc.) for shared reactive state across islands.@nanostores/vue - Use custom events (
/window.dispatchEvent
) for lightweight cross-island messaging.window.addEventListener - Use URL search params or
for state that must survive navigation.localStorage
Partial hydration vs. progressive enhancement:
Islands Architecture is a form of partial hydration — you hydrate parts of the page, not all of it. This differs from progressive enhancement, which starts with fully functional HTML and layers JS on top. In practice, combine both: your
.astro components are always progressive-enhancement-friendly, and your islands add richer interactivity.
Source
https://docs.astro.build/en/concepts/islands
Process
- Read the instructions and examples in this document.
- Apply the patterns to your implementation, adapting to your specific context.
- Verify your implementation against the details and edge cases listed above.
Harness Integration
- Type: knowledge — this skill is a reference document, not a procedural workflow.
- No tools or state — consumed as context by other skills and agents.
Success Criteria
- The patterns described in this document are applied correctly in the implementation.
- Edge cases and anti-patterns listed in this document are avoided.