Skip to content

HttpStatusCode

olegivanov edited this page May 18, 2026 · 1 revision

HttpStatusCode

1. Overview

  • Name: HttpStatusCode
  • Purpose: Declare the HTTP response status from inside the rendered tree. Writes code to the nearest <HttpStatusProvider>'s sink during component init; the SSR entry reads sink.code after await render() and applies it to the response.
  • When to use: Render-time status decisions — primarily 404 (catch-all routes), 410 (gone resources), 451 (legal-block), or any status driven by the rendered route content rather than a loader. Loader-driven errors (LoaderNotFound → 404, LoaderRedirect → 30x) keep working via ssr-data-plugin without this component.
  • 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.

2. Import and Basic Usage

<!-- routes/NotFound.svelte -->
<script lang="ts">
  import { HttpStatusCode } from "@real-router/svelte/ssr";
</script>

<HttpStatusCode code={404} />

<h1>Page Not Found</h1>
<p>Sorry, that URL doesn't match any route.</p>

On the server, mounting this component sets sink.code = 404 during init. The SSR entry reads sink.code after await render() and calls response.status(404).send(html).

On the client, <HttpStatusCode> is a silent no-op (no <HttpStatusProvider> mounted → getContext returns undefined → no write, no DOM, no warning).

3. Props

Prop Type Required Default Description
code number Yes HTTP status code to apply. Common values: 404, 410, 451, 503. Valid range: 100-999 (per Node res.end()).

4. Behavior

Init-time write

The component reads getContext<HttpStatusSink | undefined>(HTTP_STATUS_KEY) once during init. If a provider is mounted, it writes sink.code = code synchronously and renders nothing. The // svelte-ignore state_referenced_locally directive intentionally allows the one-time write — replacing code mid-render isn't supported.

Last-write-wins

When multiple <HttpStatusCode /> instances mount in the same render pass, the last one to run wins. Component init order follows template order:

<HttpStatusCode code={200} />
<HttpStatusCode code={404} />  <!-- This wins. -->

Streaming SSR is safe

Svelte 5 stable does NOT chunk-flush {#await} blocks — the server emits the full HTML in one response, including any <HttpStatusCode /> writes. The sink is therefore always written by the time await render(App, { props }) resolves. No ordering surprises with <Streamed> / <Await> boundaries.

No-op without provider

The component checks for the sink via getContext and SKIPS the write when the sink is undefined. This is the client-side runtime path<HttpStatusCode> renders inside the same component tree post-hydration, but with no provider mounted, the getContext lookup returns undefined and the component becomes a silent no-op. No DOM, no warning, no hydration mismatch.

Hydration symmetry

Svelte 5's hydration walker tolerates {#if}-branch asymmetry between server and client (verified by examples/web/svelte/ssr-examples/ssr/). The typical pattern is to mount <HttpStatusProvider> only on the server — Svelte hydrates fine without it. This contrasts with Vue/Solid which require symmetric provider mounting.

5. Examples

Glob * catch-all route → 404

<!-- routes/NotFound.svelte (matched by route name "@@notFound" or path "/*") -->
<script lang="ts">
  import { HttpStatusCode } from "@real-router/svelte/ssr";
</script>

<HttpStatusCode code={404} />

<main>
  <h1>404 — Not Found</h1>
  <a href="/">Back to home</a>
</main>

Gone (410) — deleted permanent resource

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

  import { useRoute } from "@real-router/svelte";

  const { route } = useRoute();
  const product = route.current.context.data?.product;
  const isDeleted = product?.status === "deleted";
</script>

{#if isDeleted}
  <HttpStatusCode code={410} />
  <h1>This product is no longer available</h1>
{:else}
  <ProductPage {product} />
{/if}

Unavailable for legal reasons (451)

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

  export let blocked: boolean;
</script>

{#if blocked}
  <HttpStatusCode code={451} />
  <h1>Content unavailable in your region</h1>
{/if}

Service unavailable (503) for maintenance windows

{#if maintenance.active}
  <HttpStatusCode code={503} />
  <MaintenanceBanner endsAt={maintenance.endsAt} />
{/if}

6. Server Entry Wiring

To make the status take effect, the SSR entry must:

  1. Create a sink with createHttpStatusSink().
  2. Wrap the rendered tree in <HttpStatusProvider sink={...}>.
  3. Read sink.code after await render().
// entry-server.ts
import { render } from "svelte/server";
import App from "./App.svelte";
import { createHttpStatusSink } from "@real-router/svelte/ssr";

export async function handler(req, res) {
  const sink = createHttpStatusSink();
  const router = await setupRouter(req.url);

  const output = await render(App, { props: { router, sink } });

  res.status(sink.code ?? 200).send(output.body);
}
<!-- App.svelte -->
<script lang="ts">
  import { HttpStatusProvider } from "@real-router/svelte/ssr";

  import RootRoute from "./routes/Root.svelte";

  let { router, sink } = $props();
</script>

{#if sink}
  <HttpStatusProvider {sink}>
    {#snippet children()}
      <RootRoute {router} />
    {/snippet}
  </HttpStatusProvider>
{:else}
  <RootRoute {router} />
{/if}

The {#if sink} makes the provider server-only. Svelte 5's hydration walker accepts the asymmetry between server (provider present) and client (provider absent).

7. Gotchas

  • No provider → silent no-op. Without an <HttpStatusProvider> somewhere up the tree, <HttpStatusCode /> does nothing. Verify provider wiring if status isn't applied.
  • Last write wins. Multiple <HttpStatusCode /> in the same render set the sink in template order; the last one wins. Doesn't usually matter (typical case: one component per route) but worth knowing for conditional renders.
  • Valid code range. Node's res.end() throws "Invalid status code" on NaN, 0, negative numbers, or values > 999. Pass a real HTTP status integer.
  • No mid-render updates. The sink is written once at component init. Changing code reactively doesn't update the sink — remount the component (via {#key}) to push a new value.
  • Loader-driven 404/30x is preferred when the data dictates the status. Use ssr-data-plugin's LoaderNotFound / LoaderRedirect for those cases; <HttpStatusCode> is for render-time decisions only.

8. Dependencies

  • <HttpStatusProvider> must wrap the tree (typically server-only).
  • getContext from Svelte — <HttpStatusCode> calls it during init.
  • createHttpStatusSink() from @real-router/svelte/ssr to construct the sink in the SSR entry.

9. Related

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