Streaming and async work
Deliver the route shell first, defer secondary server content, and give every async operation an owner.
Defer secondary content
Route loaders should resolve data required for navigation, SEO, and the primary shell. Deferred boundaries are for slower, optional server content such as analytics summaries, recommendations, or below-the-fold panels.
Create a meaningful boundary
Give each boundary a stable ID, a lightweight fallback, and timeout behavior when the content is optional. Create the deferred value inside request-owned rendering work. A value created at module scope would be shared by every SSR request handled by that process.
tsximport { Deferred, createDeferredValue } from "@tavojs/core";
import { Card, Skeleton, Text } from "@tavojs/ui";
async function loadStats(): Promise<{ total: number }> {
await new Promise<void>((resolve) => setTimeout(resolve, 250));
return { total: 12 };
}
export function Stats() {
const stats = createDeferredValue(loadStats(), {
id: "dashboard-stats",
timeoutMs: 1500
});
return <Deferred value={stats} fallback={<Skeleton height="6rem" />}>
{(value) => <Card title="Projects"><Text>{value.total} active projects</Text></Card>}
</Deferred>;
}Give work an owner
Navigation owns loaders and middleware, a resource owns its current load, and deferred work belongs to its signal or render lifecycle. Pass AbortSignal through every supported layer and never publish results after the owner is gone.
Treat
AbortErroras control flow, not a user-facing failure.Use transactions or idempotency keys for side effects; cancellation cannot undo a committed mutation.
Set timeouts for optional remote dependencies that should not hold a stream open.
Deferred API reference
Configure deferred values, fallbacks, timeouts, serialization, and cancellation explicitly.
tsximport {
createDeferredValue,
Deferred,
} from "@tavojs/core";
const recommendations = createDeferredValue(loadRecommendations(), {
id: "recommendations",
timeoutMs: 1500,
});
export function Recommendations() {
return (
<Deferred
value={recommendations}
fallback={<p aria-busy="true">Loading recommendations…</p>}
errorFallback={<p role="alert">Recommendations are unavailable.</p>}
>
{(items) => <RecommendationList items={items} />}
</Deferred>
);
}| API / option | Type | Default | Behavior |
|---|---|---|---|
value | T | Promise<T> | DeferredValue<T> | required | The immediate or deferred value rendered by the boundary. |
fallback | Child | null | Initial SSR and pending content. |
errorFallback | Child | (error) => Child | fallback | Replaces the boundary when the promise rejects. |
timeoutFallback | Child | (error) => Child | errorFallback | Used specifically for TAVO_DEFERRED_TIMEOUT. |
id | string | generated | Stable sharing and hydration key. Reusing an ID coordinates one promise across boundaries. |
timeoutMs | number | disabled | Positive finite timeout in milliseconds; other values do not create a timer. |
signal | AbortSignal | unset | Rejects pending work with the signal reason or AbortError. |
serialize / deserialize | functions | identity | Controls the value stored in and restored from the hydration registry. |
SSR, CSR, and hydration behavior
| API / option | Type | Default | Behavior |
|---|---|---|---|
SSR string render | HTML string | fallback | Promise-backed boundaries render fallback content synchronously. |
SSR stream | HTML chunks | stream: false | With streaming enabled, fallback arrives first and patch scripts follow as work settles. |
CSR | DOM render | fallback | Promise-backed Deferred does not coordinate browser data loading; use loaders, resources, controllers, or stores. |
Hydration | DOM hydrate | reuse server state | Resolved, rejected, and timed-out streamed state is reused without restarting the client promise. |
A timeout rejects with code TAVO_DEFERRED_TIMEOUT plus id, timeoutMs, and a safe message. Other rejections are serialized as a generic failure string; application error objects are not copied into the client document.
Production streaming contract
tscreateNodeRequestHandler({
modules,
stream: true,
document: { nonce: requestNonce }
});Streaming is disabled unless stream: true is passed to the handler.
Deferredpatch scripts and serialized state receivedocument.noncefor a strict Content Security Policy.The Node handler waits for drain when response backpressure is signaled.
A disconnected Node request aborts route work and cancels the stream reader.
Redirects are returned as a complete one-chunk document with Location metadata.
Use timeouts for optional dependencies so one remote service cannot hold the response open indefinitely.
Look up exact public types
Follow linked API names to their canonical TypeScript declarations and package boundaries.