-
-
Notifications
You must be signed in to change notification settings - Fork 0
Streaming SSR
React 19 added native streaming server-side rendering: renderToReadableStream from react-dom/server emits an HTML shell first, then streams <Suspense> boundary content as their promises resolve. The client picks up streamed chunks via inline <script> tags that swap fallback HTML for resolved content — no waiting for the slowest data fetch before the first byte is sent.
Real-Router ships a formal defer() API in @real-router/ssr-data-plugin that lets a single loader return both critical (await-before-shell) and deferred (stream-as-they-resolve) data, plus an inline-script settle wire format (<script>__rrDefer__("key","json")</script>) emitted by injectDeferredScripts from @real-router/ssr-data-plugin/server. Adapter consumers (<Await> / <Streamed> / useDeferred) live at the /ssr subpath of every adapter, e.g. @real-router/react/ssr. The router itself stays framework-agnostic — defer() is opt-in at the loader site, the wire format composes with React 19 use(promise) / Solid <Suspense> / Svelte {#await} / Vue <Suspense> + async setup() / Angular signal() without any framework-specific runtime.
This page documents the React variant end-to-end and links to the per-framework counterparts. For the alternative — RSC streaming via Flight protocol — see RSC Integration.
A typical streaming SSR page has two kinds of data:
| Kind | Where it lives | When it resolves | Example |
|---|---|---|---|
| Critical |
state.context.data (via ssr-data-plugin loader) |
Before shell renders — await router.start(url)
|
Product name, price, description |
| Deferred | Component-internal useMemo(() => fetch(...))
|
After shell — Suspense streams when promise settles | Reviews, related items, comments |
Critical data blocks shell delivery. Deferred data doesn't. The user sees critical content immediately and watches deferred sections fill in.
t=0ms request received, loader runs
t=10ms critical data resolved, renderToReadableStream returns
t=10ms shell streamed: <article><h1>Product</h1>... + Suspense fallbacks
t=600ms reviews promise resolves → React emits chunk, browser swaps fallback
t=1200ms related promise resolves → React emits chunk, browser swaps fallback
t=1200ms stream closes
The first byte (TTFB) is bounded by critical data resolution time, not by the slowest deferred promise.
Three files, no router-specific streaming wrappers.
import { defer } from "@real-router/ssr-data-plugin";
import { LoaderNotFound } from "@real-router/ssr-data-plugin/errors";
import { getProduct, fetchReviews, fetchRelated } from "../database";
import type { DataLoaderFactoryMap } from "@real-router/ssr-data-plugin";
export const REVIEWS_KEY = "reviews" as const;
export const RELATED_KEY = "related" as const;
export const loaders: DataLoaderFactoryMap = {
"products.detail": () => (params) => {
const id = params.id as string;
const product = getProduct(id);
if (!product) throw new LoaderNotFound(`product:${id}`);
return defer({
critical: { product },
deferred: {
[REVIEWS_KEY]: fetchReviews(id),
[RELATED_KEY]: fetchRelated(id),
},
});
},
};critical resolves before the shell renders (lands in state.context.data). Each deferred promise streams through inline <script>__rrDefer__("key","json")</script> settle scripts as it resolves, in resolution order — a slow promise doesn't hold a fast one. Reserved keys (__proto__/constructor/prototype) and non-promise values are rejected at validation time.
import { Await } from "@real-router/react/ssr";
import { REVIEWS_KEY } from "../router/loaders";
import type { Review } from "../database";
export function Reviews() {
return (
<Await<Review[]> name={REVIEWS_KEY}>
{(reviews) => <ReviewList items={reviews} />}
</Await>
);
}<Await name> is a thin render-prop wrapper around use(useDeferred(name)). Surrounding <Suspense> (or the cross-adapter alias <Streamed fallback={…}>) shows fallback while pending; rejection bubbles to the nearest Error Boundary. Equivalent inline form:
import { use } from "react";
import { useDeferred } from "@real-router/react/ssr";
function Reviews() {
const reviews = use(useDeferred<Review[]>("reviews"));
return <ReviewList items={reviews} />;
}Both forms read state.context.ssrDataDeferred[key]. Server: live promise from the loader (the actual Promise the loader returned). Post-hydration: registry-backed promise that resolves when the matching inline settle script lands (the bootstrap script installed in <head> populates the registry before the client bundle runs, so the promise is typically already resolved by the time use() reads it). Same call-site, two transparently different sources.
A typical production loader would put server-only delay in fetchReviews(...) itself (database query, downstream API). On the client, the registry-backed promise resolves synchronously once the settle script has landed — no hydration flash.
import { UNKNOWN_ROUTE } from "@real-router/core";
import { cloneRouter } from "@real-router/core/api";
import { serializeRouterState } from "@real-router/core/utils";
import { RouterProvider } from "@real-router/react";
import { ssrDataPluginFactory } from "@real-router/ssr-data-plugin";
import {
getDeferBootstrapScript,
injectDeferredScripts,
} from "@real-router/ssr-data-plugin/server";
import { renderToReadableStream } from "react-dom/server";
const baseRouter = createAppRouter();
export async function render(url: string) {
const router = cloneRouter(baseRouter);
router.usePlugin(ssrDataPluginFactory(loaders));
const state = await router.start(url);
const reactStream = await renderToReadableStream(
<RouterProvider router={router}>
<App />
</RouterProvider>,
);
const deferred =
(state.context as { ssrDataDeferred?: Record<string, Promise<unknown>> })
.ssrDataDeferred ?? {};
// Wrap the React stream — settle scripts ride the same chunked transfer,
// interleaved with React's body chunks in promise-resolution order.
const stream = injectDeferredScripts(reactStream, deferred, {
bootstrap: false, // emit bootstrap separately into <head> for clean React hydration
});
// Strip live promises from the JSON state — only the keys list ships in
// __SSR_STATE__; the client plugin reconstructs registry-backed promises
// from the keys (and the inline settle scripts resolve them).
const ssrJson = serializeRouterState(state, {
excludeContext: ["ssrDataDeferred"],
});
return {
stream,
ssrJson,
statusCode: state.name === UNKNOWN_ROUTE ? 404 : 200,
deferBootstrap: Object.keys(deferred).length > 0
? `<script>${getDeferBootstrapScript()}</script>`
: "",
cleanup: () => router.dispose(),
};
}await renderToReadableStream(...) returns when the shell is ready (post-use(promise) first-render but pre-Suspense-resolution). injectDeferredScripts(stream, deferred) returns a new ReadableStream<Uint8Array> that forwards every byte of the underlying React stream and interleaves <script>__rrDefer__("key", "json")</script> tags as each deferred promise settles. The combined stream stays open until both the React stream is exhausted AND every deferred promise has settled.
app.get("/{*path}", async (req, res) => {
const { stream, ssrJson, statusCode, deferBootstrap, cleanup } =
await module.render(req.originalUrl);
const ssrScript = `<script>window.__SSR_STATE__=${ssrJson}</script>`;
const templateWithState = template
.replace("<!--defer-bootstrap-->", deferBootstrap) // installs __rrDeferRegistry__ in <head>
.replace("<!--ssr-state-->", ssrScript);
const [headPart, footerPart] = templateWithState.split("<!--ssr-outlet-->");
res.status(statusCode);
res.set("Content-Type", "text/html; charset=utf-8");
res.set("Transfer-Encoding", "chunked");
res.write(headPart);
const reader = stream.getReader();
for (;;) {
const { done, value } = await reader.read();
if (done) break;
res.write(Buffer.from(value));
}
res.write(footerPart);
res.end();
cleanup();
});The server splits the HTML template at <!--ssr-outlet-->, writes the head immediately (including the defer-registry bootstrap), pipes React's streamed chunks plus the inline settle scripts into the body, then writes the footer. The bootstrap defines __rrDefer__/__rrDeferError__ global functions on globalThis before any settle script runs, so each settle resolves its registry entry deterministically. Browsers begin parsing as bytes arrive.
Note index.html needs a placeholder in <head>:
<head>
<title>...</title>
<!--defer-bootstrap-->
</head>import { hydrateRouter } from "@real-router/core/utils";
import { RouterProvider } from "@real-router/react";
import { ssrDataPluginFactory } from "@real-router/ssr-data-plugin";
import { hydrateRoot } from "react-dom/client";
const router = createAppRouter();
router.usePlugin(ssrDataPluginFactory(loaders));
const ssrState = window.__SSR_STATE__;
await hydrateRouter(router, ssrState);
hydrateRoot(
document.querySelector("#root")!,
<RouterProvider router={router}>
<App />
</RouterProvider>,
);ssr-data-plugin runs on the client too — hydrateRouter(router, ssrState) calls router.start(state.path) which triggers the loader, repopulating state.context.data with the same critical data the server resolved. Hydration sees identical state.
// loader
export async function loader() {
return defer({
critical: await fetchCritical(),
deferred: fetchDeferred(),
});
}
// component
const data = useLoaderData<typeof loader>();
return (
<>
<Critical data={data.critical} />
<Suspense fallback={<Spinner />}>
<Await resolve={data.deferred}>
{(value) => <Deferred value={value} />}
</Await>
</Suspense>
</>
);defer() is a marker that tells RR7 to wait on critical fields and stream deferred ones. <Await> is a render-prop wrapper around use(promise). Wire format is RR7-internal (turbo-stream-based).
// loader — critical AND deferred in one return
import { defer } from "@real-router/ssr-data-plugin";
export const loaders: DataLoaderFactoryMap = {
"products.detail": () => (params) =>
defer({
critical: { product: getProduct(params.id as string) },
deferred: {
reviews: fetchReviews(params.id as string),
related: fetchRelated(params.id as string),
},
}),
};
// component
import { Await, Streamed } from "@real-router/react/ssr";
function ProductDetail() {
const { route } = useRoute<{ id: string }>();
const { product } = route.context.data as { product: Product };
return (
<article>
<Critical product={product} />
<Streamed fallback={<Spinner />}>
<Await<Review[]> name="reviews">
{(reviews) => <ReviewList items={reviews} />}
</Await>
</Streamed>
</article>
);
}Same defer({ critical, deferred }) shape, same <Await name> consumer. Differences:
-
Wire format is open and framework-agnostic —
<script>__rrDefer__("key","json")</script>settle scripts (industry-standard inline-settle approach used by Remix/RR7/TanStack Start). Browser executes them at HTML parse time, no custom client parser needed. Same wire format consumed by Preact (via thenable-throw<Await>), Vue (viaasync setup), Svelte (via{#await}), Solid (viacreateResource), Angular (viainjectDeferred()signal). One server-sideinjectDeferredScriptscall works across all 6 adapters. -
<Streamed>is a cross-adapter alias for<Suspense fallback={…}>— pick<Streamed>for naming consistency with@real-router/{preact,solid,vue,svelte}/ssr, or use plain<Suspense>if you prefer one fewer abstraction. -
useDeferred(key)is exposed for directuse()calls —<Await>is a render-prop ergonomic overuse(useDeferred(name)); both forms produce identical DOM.
For the inline form use(useDeferred(name)), see the consumer snippet under "Suspense consumer with <Await> + useDeferred" above.
A Vue 3 port of this example lives at examples/web/vue/ssr-examples/ssr-streaming/ — same cloneRouter() per request, same ssrDataPluginFactory(loaders) for critical data, same serializeRouterState + hydrateRouter round-trip. The router-side code is identical; what changes is the framework's streaming primitive.
| React 19 (this page) | Vue 3 (counterpart) | |
|---|---|---|
| Streaming primitive | renderToReadableStream |
vue/server-renderer.renderToWebStream |
| Deferred-data API |
useMemo(() => fetchX()) + use(promise)
|
async setup() with top-level await fetchX()
|
<Suspense> semantics in SSR |
Non-blocking — emits fallback marker (<!--$?-->), real content follows in a later chunk; client swaps via inline <script>
|
Blocking — render of content after the boundary waits for every async setup() inside it to resolve before more HTML is emitted |
| Selective hydration | Yes — hydrates resolved islands as chunks arrive | No — app.mount("#root") hydrates the whole tree atomically |
| Error boundary |
componentDidCatch (class component) |
onErrorCaptured (returning false stops propagation) |
What this means in practice: the Vue example streams chunks of HTML as the render tree resolves (better TTFB than buffered renderToString), and <Suspense> provides the canonical Vue pattern for awaitable deferred data inside a route. But you don't get the "fallback now, real content later, hydrate it independently" model that React 19 ships. True out-of-order streaming + lazy hydration in Vue is on the Vapor mode roadmap; the current example is honest about that gap rather than papering over it.
For everything that doesn't depend on streaming-primitive semantics — per-request isolation, plugin-driven critical data, hydration round-trip — the React and Vue examples are interchangeable. The Vue port required zero changes to @real-router/vue or @real-router/ssr-data-plugin.
A Solid port of this example lives at examples/web/solid/ssr-examples/ssr-streaming/. Same cloneRouter() per request, same ssrDataPluginFactory(loaders) for critical data, same serializeRouterState + hydrateRouter round-trip. The streaming model is the closest non-React analogue: true out-of-order Suspense placeholders + selective hydration, with a different runtime mechanic.
| React 19 (this page) | Solid (counterpart) | |
|---|---|---|
| Streaming primitive |
renderToReadableStream (Web Streams) |
solid-js/web.renderToStream (pipe() Node-shape sink + a tiny end() shim, bridged into a TransformStream<Uint8Array> via TextEncoder) |
| Deferred-data API |
useMemo(() => fetchX()) + use(promise)
|
createResource(() => key, fetchX) accessor — Suspense pauses on first read |
<Suspense> semantics in SSR |
Non-blocking — emits fallback marker (<!--$?-->); real content follows in a later chunk and React swaps via inline <script>
|
Non-blocking — emits the fallback inline, then ships <template id="..."> chunks with the resolved subtree plus inline $df("…") splice scripts that run as soon as the chunk arrives |
| Selective hydration | Yes — hydrates resolved islands as chunks arrive | Yes — _$HY runtime (bootstrap injected by generateHydrationScript()) attaches per-island patches as the stream progresses |
| Error boundary |
componentDidCatch (class component) |
<ErrorBoundary fallback={(err, reset) => …}> (first-class component, no class boilerplate) |
| Hydration script | Inlined automatically by renderToReadableStream
|
Mandatory explicit generateHydrationScript() injection into <head> ahead of body |
What this means in practice: the Solid example produces a streamed HTML body where every <Suspense> ships its fallback first, then the real subtree arrives in a later HTTP chunk wrapped in a <template id="…"> element. A tiny inline $df("…") script splices the template into the placeholder, and _$HY keeps track of which islands have hydrated. The rest of the page is interactive while deferred sections finish — same UX as React 19, different runtime mechanism.
Two implementation details are unique to Solid: generateHydrationScript() is mandatory and explicit (React inlines it via the renderer; Solid asks you to place it), and the writable handed to pipe() must implement both write(chunk) and end() — the published TS surface narrows to { write } but the runtime calls writable.end() once onCompleteAll fires. Both are documented in the example README; neither requires changes to @real-router/solid or @real-router/ssr-data-plugin.
For everything that doesn't depend on streaming-primitive semantics — per-request isolation, plugin-driven critical data, hydration round-trip — the React and Solid examples are interchangeable.
A Svelte 5 port of this example lives at examples/web/svelte/ssr-examples/ssr-streaming/. Same cloneRouter() per request, same ssrDataPluginFactory(loaders) for critical data, same serializeRouterState + hydrateRouter round-trip. The streaming model is fundamentally different from React 19/Solid — Svelte 5 stable does not stream chunked HTTP.
| React 19 (this page) | Svelte 5 (counterpart) | |
|---|---|---|
| Streaming primitive |
renderToReadableStream (Web Streams) |
svelte/server.render — single buffered HTML response |
| Deferred-data API |
useMemo(() => fetchX()) + use(promise)
|
{#await fetchX()} block (template-level) or <svelte:boundary> + pending snippet |
<Suspense> semantics in SSR |
Non-blocking — emits fallback marker, real content follows in a later chunk; client swaps via inline <script>
|
Pending branch only — server renders the {#await} template's pending case and returns immediately; async resolution does NOT happen on the server
|
| Selective hydration | Yes — hydrates resolved islands as chunks arrive | No — hydrate() claims the full tree atomically; deferred sections resolve after that on the client |
| Network model | Streaming (chunked HTTP) | RSC-like — server shell, client data |
| Error boundary |
componentDidCatch (class component) |
{:catch error} branch on {#await} blocks, plus <svelte:boundary> + {#snippet failed(error, reset)} for component-level failures |
| Hydration script | Inlined automatically by renderToReadableStream
|
None needed — Svelte 5 hydration markers are inline in the body output |
What this means in practice: the streamed HTML body does not contain data-review-id="r1" — Svelte ships the reviews-fallback placeholder, the fetchReviews() Promise resumes during client hydration (returning Promise.resolve() immediately on the client), and the browser DOM updates to the resolved reviews-section. The HTTP-level proof in the example's e2e suite checks for the fallback in the response, then asserts that the resolved sections become visible after page.goto(...).
This is the architectural opposite of React 19 streaming: React optimizes for "fallback now over the wire, real content streamed in chunks later"; Svelte 5 optimizes for "single fast response with pending UI, client takes over for real data". Both models hide the same network round-trips from the user; they spend the budget differently.
The reasoning behind documenting Svelte's pending-first model honestly (rather than papering over it with router-level workarounds) is the same as for Vue's blocking Suspense: framework choice is a load-bearing constraint, and the right router posture is to delegate to native primitives — {#await}, <svelte:boundary>, hydratable() (when needed) — instead of inventing wrappers. Standalone Svelte SSR with deferred-data semantics through native primitives, without SvelteKit framework lock-in.
For everything that doesn't depend on streaming-primitive semantics — per-request isolation, plugin-driven critical data, hydration round-trip — the React and Svelte examples are interchangeable. The Svelte port required zero changes to @real-router/svelte or @real-router/ssr-data-plugin.
An Angular 21 port of this example lives at examples/web/angular/ssr-examples/ssr-streaming/. Same provideRealRouterFactory({ baseRouter, plugins }) per request, same ssrDataPluginFactory(loaders) for critical data, same cloneRouter per-request isolation. The streaming model is fundamentally different from React 19/Vue/Solid/Svelte — Angular uses per-@defer block lazy hydration rather than chunked streaming or pending snippets.
| React 19 (this page) | Angular 21 (counterpart) | |
|---|---|---|
| Streaming primitive |
renderToReadableStream (Web Streams) |
AngularNodeAppEngine.handle(req) → Response.body (ReadableStream) + writeResponseToNodeResponse bridge to Node res
|
| Deferred-data API |
useMemo(() => fetchX()) + use(promise)
|
@defer (on viewport|hover|interaction|idle|timer) template blocks with @placeholder / @loading / @error
|
<Suspense> semantics in SSR |
Non-blocking — emits fallback marker, real content follows in a later chunk |
@placeholder content shipped server-side; the real component is downloaded as a separate JS chunk and hydrated when the trigger fires (viewport, hover, interaction, idle, timer) |
| Selective hydration | Yes — hydrates resolved islands as chunks arrive |
Yes — provideClientHydration(withIncrementalHydration()) + hydrate on <trigger> syntax. Each @defer block hydrates independently on its trigger; the rest of the tree hydrates eagerly |
| Network model | Streaming (chunked HTTP) | Streaming HTTP + lazy JS chunks per @defer block |
| Error boundary |
componentDidCatch (class component) |
@error { ... } branch in @defer blocks |
| Hydration script | Inlined automatically by renderToReadableStream
|
Inlined automatically by @angular/ssr
|
What this means in practice: the streamed HTML body for /products/1 contains the critical product info (name, price, description) plus the @placeholder blocks for Reviews and RelatedItems. The browser receives the response, hydrates the eager parts, and then:
- The
@defer (on viewport)block for Reviews fires itsIntersectionObserverimmediately if the placeholder is in viewport at hydration time → downloads the Reviews chunk → hydrates →reviews-sectionreplacesreviews-fallback. If the placeholder is below the fold, the trigger fires when the user scrolls. - The
@defer (on hover)block for RelatedItems waits for an actualmouseenterevent on the placeholder → downloads the RelatedItems chunk → hydrates →related-sectionreplacesrelated-fallback.
The HTTP-level proof in the example's e2e suite checks the raw response body for the placeholders (reviews-fallback, related-fallback) plus the absence of the resolved sections (reviews-section, related-section); browser-level tests then assert that the resolved sections appear after viewport / hover triggers fire.
This model is most similar to React 19's selective hydration in spirit, but the trigger granularity is explicit and per-block (declared in template syntax via @defer (on <trigger>)) rather than implicit per-Suspense-boundary. The trade-off: more upfront declaration, more predictable hydration timing, no out-of-order placeholder swap (the placeholder DOM is replaced atomically when its block hydrates, not progressively as chunks arrive).
The reasoning behind documenting Angular's per-defer-block model (rather than aliasing it with React/Vue Suspense semantics) is the same as for Vue/Svelte: framework choice is a load-bearing constraint, and the right router posture is to delegate to native primitives — @defer, withIncrementalHydration(), AngularNodeAppEngine — instead of inventing wrappers. Standalone Angular SSR with view-agnostic routing through native Angular primitives.
For everything that doesn't depend on streaming-primitive semantics — per-request isolation, plugin-driven critical data, cookie-based DI — the React and Angular examples are interchangeable. The Angular port required zero changes to @real-router/angular adapter beyond the new provideRealRouterFactory API (added in #582 to support per-request DI scope) and zero changes to @real-router/ssr-data-plugin.
Good fit:
- Slow non-critical sections — comments, related products, recommendations, analytics widgets. Critical content (the page's reason for existing) renders immediately; secondary sections fill in.
- Time-to-interactive optimization — the user can start reading before all data arrives.
- Cascading data — primary data resolves fast, secondary data depends on primary and is slower.
Bad fit:
-
Render-blocking critical data — just
awaitit in the loader. No benefit to deferring data the user must see. -
Below-the-fold content —
<Suspense>is render-time; streaming doesn't help if the user has to scroll. Combine withIntersectionObserverfor true lazy rendering. -
Static content — if data is sync-available,
<Suspense>adds overhead with no benefit.
A complete working example:
examples/web/react/ssr-examples/ssr-streaming/
- Express + Vite SSR setup
-
ssr-data-pluginfor critical product data - Suspense +
use(promise)for deferred reviews and related items - Server-only artificial delays (600 ms reviews, 1200 ms related items) to make streaming observable
- 5 Playwright scenarios covering shell timing, streaming markers, deferred visibility, hydration, and full-reload navigation
Run pnpm test:e2e from the example directory to see the streaming behavior verified end-to-end.
-
Server-Side Rendering — classical (non-streaming) SSR primitives —
cloneRouter,start,dispose -
SSR Hydration —
serializeRouterState+hydrateRouterlifecycle -
Data Loading — overall data-loading patterns including
ssr-data-plugin -
RSC Integration — React Server Components alternative for streaming, via
@real-router/rsc-server-plugin - @real-router/ssr-data-plugin — per-route critical data loading reference
- React 19 docs:
renderToReadableStream,use(promise),<Suspense>
- 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