-
-
Notifications
You must be signed in to change notification settings - Fork 0
ServerOnly
-
Name:
ServerOnly -
Purpose: Render
childrenon the server and remove them after hydration on the client. The symmetric inverse ofClientOnly. - When to use: SEO meta strips, server-only progressive-enhancement content, no-JS fallbacks, or any tree that should appear in the initial HTML but should disappear once the SPA takes over.
-
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 { ServerOnly } from "@real-router/svelte/ssr";
import SeoMetaStrip from "./SeoMetaStrip.svelte";
</script>
<ServerOnly>
{#snippet children()}
<SeoMetaStrip />
{/snippet}
</ServerOnly>On the server, <SeoMetaStrip /> is part of the HTML. After hydration on the client, an $effect flips mounted = true and the content is removed (or replaced by fallback if provided).
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
children |
Snippet |
Yes | — | Snippet rendered on the server and during hydration; removed once $effect fires. |
fallback |
Snippet |
No | undefined |
Optional snippet that REPLACES children after hydration. Omit to render nothing on the client. |
<script lang="ts">
let mounted = $state(false);
$effect(() => {
mounted = true;
});
</script>
{#if !mounted}
{@render children()}
{:else if fallback}
{@render fallback()}
{/if}- Server render:
mounted = false→ emitschildren. - Initial client render (pre-effect):
mounted = false→ matches server HTML during hydration. - Post-effect:
mounted = true→ swaps tofallbackor renders nothing.
Svelte 5's hydration walker accepts the {#if !mounted} branch matching during the initial pass. After $effect fires, the branch flip is treated as a normal reactive update (DOM nodes are removed/replaced), not a hydration mismatch.
This is the standard pattern for "show meta info in SSR HTML but don't keep it in the interactive DOM":
- Search crawlers see
<SeoMetaStrip>in the initial HTML. - Browsers that hydrate normally see it briefly, then it disappears.
- No-JS browsers see it permanently (since
$effectnever runs without JS).
<ServerOnly> doubles as a no-JS fallback. Anything inside children works without client hydration:
<ServerOnly>
{#snippet children()}
<noscript>
<form action="/login" method="POST">
<!-- Pure-HTML form for users without JS -->
</form>
</noscript>
{/snippet}
</ServerOnly><script lang="ts">
import { ServerOnly } from "@real-router/svelte/ssr";
import type { Product } from "./types";
export let product: Product;
</script>
<ServerOnly>
{#snippet children()}
<div class="sr-only" aria-hidden="true">
<h1>{product.title}</h1>
<p>{product.description}</p>
<span>Price: {product.price}</span>
<ul>
{#each product.tags as tag}
<li>{tag}</li>
{/each}
</ul>
</div>
{/snippet}
</ServerOnly><ServerOnly>
{#snippet children()}
<article>
<h1>{title}</h1>
<p>{fullBody}</p>
</article>
{/snippet}
{#snippet fallback()}
<article>
<h1>{title}</h1>
<p>{truncatedBody}</p>
<button onclick={expand}>Read more</button>
</article>
{/snippet}
</ServerOnly>The server emits the full content (good for SEO); the client swaps to a truncated interactive variant.
<!-- SEO-friendly server content -->
<ServerOnly>
{#snippet children()}
<SeoMetaStrip />
{/snippet}
</ServerOnly>
<!-- Browser-only interactive content -->
<ClientOnly>
{#snippet children()}
<InteractiveMap />
{/snippet}
</ClientOnly>-
Brief flash during hydration.
childrenappears in the HTML and stays until$effectruns (post-hydration tick). If the swap is visually noticeable, use a matchingfallbackto keep layout stable. -
childrenis the SSR content, NOT a placeholder. Different mental model from<ClientOnly>: therechildrenis the desired client UI; herechildrenis the desired server UI. -
No-JS users see
childrenforever. Often desired (progressive enhancement). If you need different behavior, add a<noscript>block insidechildren. -
$effectdoesn't run on the server. Don't put server-side logic in$effectand expect it to fire — the rune is a client-side primitive. -
Hydration warnings. Svelte 5 generally tolerates the
{#if !mounted}flip. If your tree has client-only reactive state that conflicts with the server-renderedchildren, isolate that state inside thefallbackbranch.
- No
<RouterProvider>requirement. - Requires Svelte 5.x for runes.
- ClientOnly — inverse: server fallback, client children
- 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