Client directives
A client:* directive on a component tells the islands plugin to hydrate it — and when. Components without a client:* directive render to static HTML and ship no client JavaScript.
Enable the attributes with a one-time type import — see Installation:
import '@sigx/ssr-islands/jsx';
The strategies
<Counter client:load /> {/* hydrate immediately */}
<Sidebar client:idle /> {/* hydrate when the browser is idle */}
<Chart client:visible /> {/* hydrate when scrolled into view */}
<Menu client:media="(min-width: 768px)" /> {/* hydrate when the query matches */}
<Search client:interaction /> {/* hydrate on the first interaction */}
<LiveFeed client:only /> {/* skip SSR, mount fresh on the client */}
client:load
Hydrates as soon as hydrateIslands() runs. Use for above-the-fold interactivity
that must be live immediately (primary nav, a hero CTA).
client:idle
Defers hydration to browser idle time (requestIdleCallback, with a timeout
fallback). Good for secondary widgets that should not compete with first paint.
client:visible
Hydrates when the element scrolls into the viewport, via IntersectionObserver.
Ideal for below-the-fold islands — charts, comment threads, carousels — so their
JS only loads when the user reaches them. Pairs naturally with lazy registration
for per-island code splitting.
client:media
Hydrates when a CSS media query matches. Pass the query as the value:
<MobileMenu client:media="(max-width: 767px)" />
<DesktopNav client:media="(min-width: 768px)" />
The component stays static until (and unless) the query matches — so a desktop visitor never pays for the mobile menu's JavaScript, and vice versa.
client:interaction
Hydrates on the first interaction with the element — the first pointerdown,
keydown, touchstart, or focusin. It fires once, and the triggering event is
not replayed, so the island wakes and the user's next action lands on the live
component. Ideal for controls that are visible but only matter once touched — a
search box, a "reply" affordance, a menu that opens on click — where you want the
static HTML immediately and the JavaScript only when the user commits.
client:only
Genuinely skips server rendering. The component is not run on the server:
its setup/render never execute, and the server emits an empty
<div data-boundary> placeholder in its place — no server HTML for the component
itself. The island still gets a boundary record in __SIGX_BOUNDARIES__ (so the
client knows to mount it), but with no captured signal state, since nothing
ran server-side. On the client it mounts fresh.
Use it for components that cannot render on the server — ones that read window,
localStorage, or other browser-only globals during setup.
Contrast with client:load, which does render on the server (you get the SSR
HTML immediately) and then hydrates that existing DOM, resuming from the captured
signal state. Reach for client:only only when server rendering is impossible or
unwanted; otherwise a server-rendered strategy gives a faster first paint.
The strategy type
The six strategies are the HydrationStrategy union, and the directives are the
ClientDirectives interface that augments component attributes:
type HydrationStrategy = 'load' | 'idle' | 'visible' | 'media' | 'interaction' | 'only';
interface ClientDirectives {
'client:load'?: boolean;
'client:idle'?: boolean;
'client:visible'?: boolean;
'client:media'?: string; // the media query
'client:interaction'?: boolean;
'client:only'?: boolean;
}
Directives vs. boundary axes
These directives are the islands pack's friendly surface over the two boundary
axes. The two vocabularies don't
line up one-to-one, and it's worth knowing why: client:only is a flush
concern — it maps to { flush: 'skip', hydrate: 'load' } — while the boundary
model's hydrate: 'never' (server HTML, no client component) has no directive of
its own. So don't read the directive list and the hydrate axis as the same enum.
Cleaning up on SPA navigation
Deferred strategies register observers and listeners (IntersectionObserver,
matchMedia) for islands that have not triggered yet. When you navigate away in
an SPA before they fire, call cleanupPendingHydrations() to tear those down and
avoid leaks:
import { cleanupPendingHydrations } from '@sigx/ssr-islands';
router.beforeEach(() => {
cleanupPendingHydrations();
});
Next steps
- Registry & code splitting — make islands' components resolvable on the client.
- Plugin setup — register the server plugin and transfer signal state.
- API reference
