Skip to content
olegivanov edited this page May 18, 2026 · 1 revision

Await

1. Overview

  • Name: Await
  • Purpose: Render a children snippet with the resolved value of a deferred SSR promise, with an optional fallback while 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.

2. Import and Basic Usage

<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>

3. Props

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.

4. Behavior

Underlying implementation

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}

Reactive name

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).

SSR streaming semantics

  • 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, and children(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.

Missing key → forever-pending

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.

Rejection bubbles

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.

5. Examples

With SSR data loader

// 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}

Custom rejection handling via {#await} directly

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}

Dynamic key via $state

<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.

6. Gotchas

  • 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 fallback shows indefinitely if name doesn'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.
  • children is a positional snippet. {#snippet children(value)} — single argument. Snippets without (value) won't compile.

7. Dependencies

  • Requires <RouterProvider> mounted with an ssr-data-plugin-aware router. useDeferred calls useRoute() internally, which throws outside a provider.
  • Loader must declare the corresponding key in defer({ deferred: { <key>: Promise } }).

8. Related

  • 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 — the defer() server-side primitive

Navigation

Home


Concepts


Getting Started


Router Methods

Lifecycle

Navigation

State

URL & Path

Events


Standalone API

Tree-shakeable functions — import only what you need.

Routes — getRoutesApi(router)

Dependencies — getDependenciesApi(router)

Guards — getLifecycleApi(router)

Plugin Infrastructure — getPluginApi(router)

For plugin authors, not for general use.

SSR / SSG


React / Preact / Solid / Vue / Svelte Integration

Provider

Hooks

Components

SSR Components & Hooks

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

DOM Utilities

Patterns


Subscription Layer (@real-router/sources)


Reactive Streams (@real-router/rx)


Plugins

Browser Plugin

Navigation Plugin

Hash Plugin

Memory Plugin

Lifecycle Plugin

Preload Plugin

Logger Plugin

Persistent Params

SSR Data

RSC Server

Validation

Search Schema

Utilities


Reference

Types

Error Codes

Clone this wiki locally