-
-
Notifications
You must be signed in to change notification settings - Fork 0
ClientOnly
-
Name:
ClientOnly -
Purpose: Render a
fallback(default:null) on the server, swap tochildrenafter hydration on the client. -
When to use: Wrap UI that depends on browser-only APIs (
window,localStorage,IntersectionObserver, geolocation, etc.) and would crash or differ between server and client. -
Availability: All 5 framework adapters —
@real-router/react/ssr,@real-router/preact/ssr,@real-router/solid/ssr,@real-router/vue/ssr,@real-router/svelte/ssr. Semantics identical across adapters.
<script lang="ts">
import { ClientOnly } from "@real-router/svelte/ssr";
import BrowserApiWidget from "./BrowserApiWidget.svelte";
import Skeleton from "./Skeleton.svelte";
</script>
<ClientOnly>
{#snippet children()}
<BrowserApiWidget />
{/snippet}
{#snippet fallback()}
<Skeleton />
{/snippet}
</ClientOnly>On the server, only the Skeleton is emitted to HTML. Once the component hydrates on the client, an $effect flips an internal mounted flag and BrowserApiWidget replaces the skeleton.
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children |
Snippet |
Yes | — | Snippet rendered only on the client (after hydration). |
fallback |
Snippet |
No | undefined |
Snippet rendered on the server and immediately after hydration before the $effect fires. Omit to emit nothing on the server. |
<script lang="ts">
let mounted = $state(false);
$effect(() => {
mounted = true;
});
</script>
{#if mounted}
{@render children()}
{:else if fallback}
{@render fallback()}
{/if}-
$effectdoes NOT run on the server (Svelte's SSR compiler emits the rune as a no-op). - Initial render produces
mounted = false→ fallback (or nothing) ships. - Client mounts the component,
$effectfires,mounted = true→ re-renders withchildren.
ClientOnly is hydration-safe in Svelte 5: the framework tolerates the {#if mounted} branch flipping between server (false) and client (true after effect). No console.error warnings, no DOM-mismatch crashes — verified by examples/web/svelte/ssr-examples/ssr/.
This contrasts with Vue/Solid which require symmetric provider mounting; Svelte's hydration walker accepts asymmetric branches.
Content rendered inside <ClientOnly> does NOT appear in the SSR HTML. Search engines and link previewers see only the fallback. Move SEO-critical content out of <ClientOnly>.
<script lang="ts">
import { ClientOnly } from "@real-router/svelte/ssr";
import Map from "./Map.svelte"; // uses window.navigator.geolocation
</script>
<ClientOnly>
{#snippet children()}
<Map />
{/snippet}
{#snippet fallback()}
<div class="map-skeleton" aria-label="Loading map">Loading map…</div>
{/snippet}
</ClientOnly><ClientOnly>
{#snippet children()}
<PortalMount />
{/snippet}
</ClientOnly><script lang="ts">
import { ClientOnly } from "@real-router/svelte/ssr";
</script>
<ClientOnly>
{#snippet children()}
{@const theme = localStorage.getItem("theme") ?? "light"}
<ThemeToggle initial={theme} />
{/snippet}
{#snippet fallback()}
<ThemeToggle initial="light" />
{/snippet}
</ClientOnly><ServerOnly>
{#snippet children()}
<SeoMetaStrip />
{/snippet}
</ServerOnly>
<ClientOnly>
{#snippet children()}
<InteractiveDashboard />
{/snippet}
</ClientOnly>-
No SSR content for SEO. Content inside
childrenonly exists post-hydration. If the content matters for SEO/social previews, either move it out or duplicate critical info in thefallback. -
Flash on hydration. Users see
fallbackbriefly during hydration. Use a skeleton that matches the final layout (same dimensions) to avoid CLS (Cumulative Layout Shift). -
$effectis the gate. The flip happens after the initial post-hydration tick. For "render-time" client detection, this is fine — but don't rely onmountedfor synchronous logic in<script>. -
No prop to opt out of fallback on first paint. The fallback always renders on server + before-mount on client. If you need an empty render on the server but a synchronous detection on the client, use a
typeof window !== "undefined"check inside a regular component (sacrificing SSR/CSR template parity).
- No
<RouterProvider>requirement —<ClientOnly>is environment-only, not router-aware. - Requires Svelte 5.x for runes.
-
ServerOnly — symmetric inverse: renders on server, swaps to
fallbackafter hydration - Svelte Integration Guide — overview of the SSR surface
- SSR Hydration — full SSR/hydration lifecycle documentation
- View-Agnostic Design
- Core Concepts
- Navigation Lifecycle
- Guards
- Plugin Architecture
- Hash Fragment Support
- Accessibility (A11y)
- Server-Side Rendering
- Data Loading
- Streaming SSR
- SSR Cancellation
- RSC Integration
- Testing
- Glossary
Tree-shakeable functions — import only what you need.
- add (addRoute)
- remove (removeRoute)
- replace (replaceRoutes)
- clear (clearRoutes)
- get (getRoute)
- has (hasRoute)
- update (updateRoute)
- get (getDependency)
- getAll (getDependencies)
- set (setDependency)
- setAll (setDependencies)
- has (hasDependency)
- remove (removeDependency)
- reset (resetDependencies)
For plugin authors, not for general use.
- makeState
- buildState
- buildNavigationState
- forwardState
- getForwardState
- setForwardState
- matchPath
- setRootPath
- getRootPath
- navigateToState
- addEventListener
- getRouteConfig
- getOptions
- addBuildPathInterceptor
- extendRouter
- getTree
- React Integration Guide
- Preact Integration Guide
- Solid Integration Guide
- Vue Integration Guide
- Svelte Integration Guide
- Ink (Terminal UI) Integration Guide
- Desktop (Electron, Tauri) Integration Guide
- useRouter
- useRoute
- useRouteNode
- useNavigator
- useRouteUtils
- useRouterTransition
- useRouteExit
- useRouteEnter
- useRouteStore (Solid only)
- useRouteNodeStore (Solid only)
SSR-feature subpath —
@real-router/{adapter}/ssr. Symmetric across React/Preact/Solid/Vue/Svelte.
- Lazy (Svelte only — dynamic component import)
- Await — read deferred SSR data by key
- Streamed — Suspense-style boundary
- ClientOnly — server fallback → client children swap
- ServerOnly — server children, removed after hydration
- HttpStatusCode — render-time HTTP status declaration
-
HttpStatusProvider — provides sink to descendant
<HttpStatusCode> - useDeferred — read deferred Promise by key