---
title: 'Client-only components with mochi:clientOnly'
slug: client-only
description: 'Skip SSR entirely and mount a component in the browser with mochi:clientOnly.'
---
## Client-only components with `mochi:clientOnly`
Add `mochi:clientOnly` to a component that must never render on the server. SSR emits only an empty island wrapper; in the browser the component is mounted with Svelte's `mount()` (not hydrated — there is no SSR HTML to reuse). Use it for components built on browser-only APIs: `window`, canvas, `localStorage`, `requestAnimationFrame`, third-party browser SDKs.
```svelte
```
Props work exactly like `mochi:hydrate` — serialized with `devalue` and embedded into the HTML. See `Passing props to islands` for the supported types. The implicit `isHydratable` prop is injected at mount (always `true`). There is **no** `islandId` prop on a client-only island — nothing renders server-side, so there's no id to carry; for a unique id inside the component, use Svelte's `$props.id()`, which `mount()` mints fresh in the browser.
```svelte
```
### Fallback content
Pass fallback markup as children — it renders server-side as placeholder content and is removed the moment the component mounts:
```svelte
Loading chart…
```
For `svelte-check` to accept the fallback children, the client-only component types its props with `ClientOnlyProps` — it adds an optional `children` snippet so the call site type-checks without you declaring a `children` prop by hand:
```svelte
```
The fallback children are an SSR placeholder only — they are **not** passed to the component at runtime, so don't render `children` inside a client-only component. Keep the fallback to static markup: do **NOT** put `mochi:*` islands inside it, since it is wiped from the DOM when the component mounts.
### Lazy client-only with `mochi:clientOnly:visible`
Defer the browser-side `mount()` until the wrapper scrolls into the viewport. The component still never renders on the server — its JavaScript and CSS are simply fetched and mounted only when the placeholder intersects the viewport via `IntersectionObserver`.
```svelte
```
Pass `rootMargin` to start loading before the element enters the viewport — forwarded straight to `IntersectionObserver` (default `'0px'`):
```svelte
```
Provide fallback children as the placeholder — they reserve space and give the observer something to watch until the component mounts (an empty wrapper still gets a 1px minimum height so it remains observable):
```svelte
Loading chart…
```
### Server-side APIs are unavailable
The component never runs on the server, so server-only APIs — `getRequestContext()`, `cookies`, `hydratable()` server reads — are unavailable inside it. Pass any server-derived values in as props from the page.
`