Skip to content

useDeferred

olegivanov edited this page May 18, 2026 · 1 revision

useDeferred

1. Overview

  • Name: useDeferred<T>(key: string)
  • Purpose: Read a deferred Promise published by an SSR loader via defer({ deferred: { <key>: Promise } }). Returns the raw Promise<T> for use inside {#await} blocks or the higher-level <Await> / <Streamed> components.
  • When to use: Inside a route component that consumes deferred (non-critical) SSR data.
  • Availability: All 5 framework adapters expose a useDeferred composable/hook with symmetric semantics — @real-router/react/ssr, @real-router/preact/ssr, @real-router/solid/ssr, @real-router/vue/ssr, @real-router/svelte/ssr.

2. Import and Basic Usage

<script lang="ts">
  import { useDeferred } from "@real-router/svelte/ssr";

  import type { Review } from "./types";

  const reviewsPromise = useDeferred<Review[]>("reviews");
</script>

{#await reviewsPromise}
  <Spinner />
{:then reviews}
  <ReviewList items={reviews} />
{:catch error}
  <ErrorBanner message={error.message} />
{/await}

3. Signature

function useDeferred<T = unknown>(key: string): Promise<T>;
Parameter Type Description
key string Identifier matching a key in the loader's defer({ deferred: { <key>: Promise } }).

Returns: Promise<T> — the deferred Promise from state.context.ssrDataDeferred[key]. If the key is missing, returns a forever-pending Promise.

4. Behavior

Source

export function useDeferred<T = unknown>(key: string): Promise<T> {
  const { route } = useRoute();
  const context = route.current.context as DeferredContext;
  const deferred = context.ssrDataDeferred;

  return (deferred?.[key] ?? NEVER_PROMISE) as Promise<T>;
}
  • Reads state.context.ssrDataDeferred from the current route, populated by @real-router/ssr-data-plugin's defer() helper.
  • Calls useRoute() — throws if no active state (unstarted/stopped/disposed) or outside a <RouterProvider>.
  • Returns the Promise verbatim, or a forever-pending NEVER_PROMISE singleton if the key is missing.

Forever-pending on missing key (intentional)

If key doesn't match a loader-declared entry, useDeferred returns a shared NEVER_PROMISE that never resolves. This surfaces loader/consumer key drift as a visible loading state ({#await} stays in the pending branch) rather than a silent runtime error.

<!-- Loader declares "reviews", consumer asks for "reveiws" (typo) -->
{#await useDeferred("reveiws")}
  <Spinner />  <!-- Forever-pending — never resolves. -->
{:then reviews}
  …
{/await}

During development, a permanent spinner is an easy signal that the key is misspelled.

Type parameter

The <T> generic lets you type the resolved value:

interface Review {
  id: string;
  text: string;
}

const reviewsPromise = useDeferred<Review[]>("reviews");
// reviewsPromise: Promise<Review[]>

The cast is one-way — the helper doesn't validate the runtime shape. Wrong key + wrong type → forever-pending (the runtime never resolves to validate).

Must be called during component init

Like all Svelte composables in this adapter, useDeferred calls getContext (via useRoute) which requires synchronous component init. Don't call it inside $effect or async callbacks — see Svelte Integration Guide.

Reactivity

useDeferred(key) returns a plain Promise. If key is a $state rune, wrap the call in $derived to re-resolve on key change:

<script lang="ts">
  let selectedTab = $state("reviews");

  const promise = $derived(useDeferred(selectedTab));
</script>

{#await promise}
  <Spinner />
{:then data}
  <TabContent {data} />
{/await}

<Await name={selectedTab}> does this internally — see Await.

5. Examples

With {#await} and rejection handling

<script lang="ts">
  import { useDeferred } from "@real-router/svelte/ssr";

  const reviews = useDeferred<Review[]>("reviews");
</script>

{#await reviews}
  <Spinner />
{:then list}
  <ReviewList items={list} />
{:catch error}
  <ErrorBanner message={error.message} />
{/await}

With cross-adapter <Await> (no {:catch})

<script lang="ts">
  import { Await } from "@real-router/svelte/ssr";
</script>

<Await<Review[]> name="reviews">
  {#snippet children(reviews)}
    <ReviewList items={reviews} />
  {/snippet}
  {#snippet fallback()}
    <Spinner />
  {/snippet}
</Await>

<Await> builds on useDeferred — see Await for when to prefer the higher-level component.

Loader publishing deferred data

// routes/product.loader.ts
import { defer } from "@real-router/ssr-data-plugin";

export const loader = async (params) => {
  return defer({
    // Critical — awaited inline before render starts.
    product: await fetchProduct(params.id),
    // Deferred — runs in parallel, surfaces via state.context.ssrDataDeferred.
    deferred: {
      reviews: fetchReviews(params.id),
      similar: fetchSimilarProducts(params.id),
    },
  });
};
<!-- routes/Product.svelte -->
<script lang="ts">
  import { useRoute } from "@real-router/svelte";
  import { useDeferred } from "@real-router/svelte/ssr";

  const { route } = useRoute();
  const product = route.current.context.data?.product;
  const reviews = useDeferred("reviews");
  const similar = useDeferred("similar");
</script>

{#if product}
  <ProductHero {product} />

  {#await reviews}
    <Spinner label="Loading reviews" />
  {:then list}
    <ReviewList items={list} />
  {/await}

  {#await similar}
    <Spinner label="Loading recommendations" />
  {:then list}
    <SimilarProducts items={list} />
  {/await}
{/if}

Combine multiple deferred Promises

<script lang="ts">
  import { useDeferred } from "@real-router/svelte/ssr";

  const reviews = useDeferred<Review[]>("reviews");
  const ratings = useDeferred<Rating[]>("ratings");

  // Race or all-of:
  const allDataPromise = Promise.all([reviews, ratings]);
</script>

{#await allDataPromise}
  <Spinner />
{:then [reviewList, ratingList]}
  <ReviewSection reviews={reviewList} ratings={ratingList} />
{/await}

6. Gotchas

  • Missing key → forever-pending. Verify loader and consumer agree on key names. The visible spinner is the diagnostic signal.
  • Throws outside <RouterProvider> or with no active route. useDeferred calls useRoute() which throws in either case. Call only inside route components or after router.start() has resolved.
  • Must be called during component init. Don't call inside $effect, event handlers, or async callbacks — getContext fails outside init.
  • No runtime type validation. The <T> generic is a cast, not a schema. Trust the loader to publish the right shape, or validate at the await site.
  • Server-only Promise resolution by default. The deferred Promises are created on the server inside the loader. On the client side (after hydration), the same Promises continue resolving — Svelte 5 stable rehydrates the in-flight Promises rather than re-fetching. Verify with examples/web/svelte/ssr-examples/ssr-streaming/.

7. Dependencies

  • Requires <RouterProvider> mounted with ssr-data-plugin (or a custom plugin that populates state.context.ssrDataDeferred).
  • Loader must declare defer({ deferred: { <key>: Promise } }).

8. Related

  • Await — higher-level component that wraps useDeferred + {#await} with name prop
  • Streamed — Suspense-style boundary for grouping multiple deferred reads
  • Streaming SSR — cross-framework streaming model
  • Svelte Integration Guide — full SSR surface overview
  • @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