Lifecycle
Five global hooks cover a component's life. None needs an import.
onMount
Runs once, after the component is connected to the DOM — the place for focus, measurements, subscriptions, or timers.
export default function Clock() { let now = $state(Date.now()); onMount(() => { const id = setInterval(() => (now = Date.now()), 1000); return () => clearInterval(id); }); return <time>{now}</time>; }
Returning a function from onMount registers it as cleanup.
onCleanup
Runs when the component is removed from the DOM. Use it to tear down anything that outlives a render:
onCleanup(() => socket.close());
onResize
Watches the component's own element with a ResizeObserver and calls back with the ResizeObserverEntry on every size change. The observer is disconnected automatically on unmount — no manual teardown.
export default function Chart() { let width = $state(0); onResize((entry) => { width = entry.contentRect.width; }); return <svg width={width} />; }
As with the platform API, one initial entry is delivered right after mount, then one per size change.
A component's host element is display: inline by default, and inline boxes measure 0×0. Add a display: block (or similar) rule for the host in your CSS to get meaningful measurements.
onVisibilityChange
Fires when the component scrolls into or out of the viewport, via an IntersectionObserver on the component's element. The callback receives (isIntersecting, entry); the observer is disconnected automatically on unmount.
export default function LazyImage({ src }) { let visible = $state(false); onVisibilityChange((isIntersecting) => { if (isIntersecting) visible = true; }); return <img src={visible ? src : undefined} loading="lazy" />; }
onMediaQuery
Wires window.matchMedia(query) and calls back with (matches, event) — once immediately at mount with the current state, then on every change. The listener is removed automatically on unmount.
export default function Sidebar() { let compact = $state(false); onMediaQuery("(max-width: 700px)", (matches) => { compact = matches; }); return <nav class={compact ? "drawer" : "rail"}>…</nav>; }
The query expression is evaluated once at mount — a signal read inside it is a one-time snapshot, not reactive.
In pages and layouts
All five hooks work in page.jsx / layout.jsx too: mount runs after the page is inserted, cleanup runs when the router navigates away. Because onResize and onVisibilityChange need an element to observe, a page using them must have a single element root — wrap fragment views in a container element (the compiler warns otherwise). onMediaQuery has no such constraint.
Rules
Hooks run in FIFO order per kind;
onMountcallbacks run before the observer hooks are wired.Hooks are compiler macros, recognized only as top-level statements of the component or page body. A call inside a helper function, condition, or after an
awaitis a silent no-op.During SSG/SSR none of them run — they exist only in the client output.