Hydration Strategies
Advanced hydration directives and custom triggers for fine-grained control.
Overview
Avalon ships with four built-in conditions (on:client, on:visible, on:interaction, on:idle) and a media: condition covered in Islands Architecture. This page covers the custom directives that give you fine-grained control beyond those defaults.
Custom directives
Custom directives follow the on:<name> pattern and accept an optional argument via conditionArg. They are registered automatically — no setup needed.
on:delay
Hydrates after a timeout.
// Hydrate after 3 seconds (default: 1000ms)
<Widget island={{ condition: 'on:delay', conditionArg: '3000' }} />
Use when: You want to defer hydration by a fixed duration — e.g. a tooltip that isn't needed in the first few seconds.
on:scroll
Hydrates when the page scrolls past a pixel threshold.
// Hydrate when user scrolls past 500px
<LazySection island={{ condition: 'on:scroll', conditionArg: '500' }} />
Use when: The component should activate based on scroll depth rather than viewport intersection — e.g. a "back to top" button or a progress bar.
on:event
Hydrates when a custom DOM event fires on document.
// Hydrate when a custom event fires
<Dashboard island={{ condition: 'on:event', conditionArg: 'data:loaded' }} />
Use when: Hydration depends on an external signal — data finishing a fetch, a third-party script loading, or an app-level readiness event.
on:match
Hydrates when a media query matches. Similar to the built-in media: condition but uses the custom directive system and accepts the query as conditionArg.
// Hydrate when viewport is at least 1024px
<Sidebar island={{ condition: 'on:match', conditionArg: '(min-width: 1024px)' }} />
Registering your own directive
Register custom directives in your server/renderer.ts before the export:
import { registerHydrationDirective } from '@useavalon/avalon';
registerHydrationDirective('on:countdown', {
name: 'on:countdown',
script: (el, hydrate, arg) => {
const seconds = parseInt(arg || '5', 10);
let remaining = seconds;
const tick = setInterval(() => {
remaining--;
if (remaining <= 0) {
clearInterval(tick);
hydrate();
}
}, 1000);
},
});
export { default } from 'virtual:avalon/renderer';
Then use it on any island:
<MyComponent island={{ condition: 'on:countdown', conditionArg: '5' }} />
The directive's script function runs on the client. It receives:
| Parameter | Description |
|---|---|
el | The island's DOM element |
hydrate | Callback — call once to trigger hydration |
arg | The conditionArg string (or undefined) |
Performance tips
- Start with
on:interactionoron:visiblefor most islands. They cover the majority of use cases with zero upfront JS cost. - Use
on:delayto stagger hydration of multiple non-critical islands so they don't all compete for the main thread at once. - Prefer
on:eventover polling when hydration depends on async data — it avoids unnecessary timer overhead. - Avoid
on:clientunless the component truly needs to be interactive before any user action. Everyon:clientisland adds to the critical path. - Combine
on:scrollwith a high threshold for components that only matter deep in the page — this is lighter thanon:visiblewith a large root margin.