-
-
Notifications
You must be signed in to change notification settings - Fork 0
Await
-
Name:
Await -
Purpose: Render a
childrensnippet with the resolved value of a deferred SSR promise, with an optionalfallbackwhile pending. -
When to use: Inside an SSR-rendered route component, read deferred data published by an SSR loader (
defer({ deferred: { <key>: Promise } })) and surface it to the template. -
Availability: All 5 framework adapters ship an
<Await>component with symmetric semantics —@real-router/react/ssr,@real-router/preact/ssr,@real-router/solid/ssr,@real-router/vue/ssr,@real-router/svelte/ssr. The Svelte variant wraps the native{#await}block.
<script lang="ts" generics="T">
import { Await } from "@real-router/svelte/ssr";
interface Review {
id: string;
text: string;
}
</script>
<Await<Review[]> name="reviews">
{#snippet children(reviews)}
<ReviewList items={reviews} />
{/snippet}
{#snippet fallback()}
<Spinner />
{/snippet}
</Await>| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string |
Yes | — | Deferred key declared in the loader's defer({ deferred: { <name>: Promise } })
|
children |
Snippet<[T]> |
Yes | — | Render snippet for the resolved value. Receives the awaited value as its single positional argument. |
fallback |
Snippet |
No | undefined |
Snippet rendered while the deferred promise is pending. Omit for silent loading. |
Await is a thin wrapper around useDeferred(name) + Svelte's native {#await promise}…{:then value}…{/await} block:
<!-- conceptually equivalent to: -->
<script lang="ts">
import { useDeferred } from "@real-router/svelte/ssr";
const promise = useDeferred<T>(name);
</script>
{#await promise}
<Fallback />
{:then value}
<Children {value} />
{/await}The internal promise is wrapped in $derived(useDeferred<T>(name)) — passing a $state rune to name re-resolves to a different promise reactively. Note that this is different from <Link>'s routeParams (where the underlying source is bound at mount).
-
Server side: Svelte 5 stable does NOT progressively chunk-flush the HTTP response for
{#await}blocks. The server emits the fallback branch in the initial HTML and resolves the promise on the client after hydration. -
Client side (post-hydration): the
{#await}re-runs, the deferred promise resolves, andchildren(value)replaces the fallback.
This makes Svelte's <Await> behave like a deferred-data primitive: server ships the shell + pending UI in one response; client hydrates and resolves. Symmetric in API with React 19 / Solid <Streamed>/<Await> but different in network shape (no chunked streaming). See Streaming SSR for the cross-framework comparison.
useDeferred(name) returns a forever-pending Promise when the key is missing from state.context.ssrDataDeferred. The <Await> block will render the fallback indefinitely — a visible loading state, not a silent runtime error. Loader / consumer key drift is therefore visible during development.
If the deferred promise rejects, it bubbles to the nearest {:catch} handler in the surrounding {#await} chain — or to a wrapping <Streamed> boundary. <Await> itself does not provide a {:catch} branch.
// routes/product.loader.ts
import { defer } from "@real-router/ssr-data-plugin";
export const loader = async (params) => {
return defer({
// Critical data — resolved before render.
product: await fetchProduct(params.id),
deferred: {
// Non-critical, streamed in.
reviews: fetchReviews(params.id),
similar: fetchSimilarProducts(params.id),
},
});
};<!-- routes/Product.svelte -->
<script lang="ts">
import { Await, Streamed } from "@real-router/svelte/ssr";
import { useRoute } from "@real-router/svelte";
import Spinner from "./Spinner.svelte";
import ReviewList from "./ReviewList.svelte";
import SimilarProducts from "./SimilarProducts.svelte";
const { route } = useRoute();
const product = route.current.context.data?.product;
</script>
{#if product}
<ProductHero {product} />
<Await name="reviews">
{#snippet children(reviews)}
<ReviewList items={reviews} />
{/snippet}
{#snippet fallback()}
<Spinner label="Loading reviews" />
{/snippet}
</Await>
<Await name="similar">
{#snippet children(items)}
<SimilarProducts {items} />
{/snippet}
</Await>
{/if}For rich error UX, skip <Await> and use {#await} directly (gain access to {:catch}):
<script lang="ts">
import { useDeferred } from "@real-router/svelte/ssr";
const reviews = useDeferred("reviews");
</script>
{#await reviews}
<Spinner />
{:then list}
<ReviewList items={list} />
{:catch error}
<ErrorBanner message={error.message} />
{/await}<script lang="ts">
let selectedTab = $state("reviews");
</script>
<button onclick={() => (selectedTab = "questions")}>Questions</button>
<button onclick={() => (selectedTab = "reviews")}>Reviews</button>
<Await name={selectedTab}>
{#snippet children(items)}
<TabContent {items} />
{/snippet}
{#snippet fallback()}
<Spinner />
{/snippet}
</Await>The $derived wrapper inside <Await> re-resolves to the new deferred when selectedTab changes.
-
No
{:catch}branch in<Await>itself. Rejection bubbles up; wrap with<Streamed>(which uses{#await}'s{:catch}indirectly via Svelte's error-boundary chain) or use raw{#await}for fine-grained handling. -
Forever-pending on missing key. The
fallbackshows indefinitely ifnamedoesn't match a deferred entry. Verify the loader and consumer agree on key names. - No server-side streaming. Svelte 5 stable doesn't chunk-flush — fallback ships in the SSR HTML, real content resolves after hydration. For framework streaming comparison see Streaming SSR.
-
childrenis a positional snippet.{#snippet children(value)}— single argument. Snippets without(value)won't compile.
- Requires
<RouterProvider>mounted with anssr-data-plugin-aware router.useDeferredcallsuseRoute()internally, which throws outside a provider. - Loader must declare the corresponding key in
defer({ deferred: { <key>: Promise } }).
-
useDeferred — the lower-level composable
<Await>builds on -
Streamed — wrapper that pairs with
<Await>for cross-framework symmetry (no-op in Svelte; uses{#await}directly) - Streaming SSR — framework-by-framework breakdown of deferred-data semantics
- Svelte Integration Guide — overview of the SSR surface
-
@real-router/ssr-data-plugin— thedefer()server-side primitive
- 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