#Using Suspense
Estimated reading time: 3 minutesImport Suspense and renderToStream from @kitajs/html/suspense.
#Basic usage
Wrap async components in a Suspense element with a rid, a fallback, and optionally a
catch handler. Then pass the tree to renderToStream, which returns a Readable
stream.
async function function UserStats({ id }: {
id: string;
}): Promise<string>
UserStats ({ id: string id }: { id: string id : string }) {
const const stats: {
totalPosts: number;
}
stats = await let api: {
getStats(id: string): Promise<{
totalPosts: number;
}>;
}
api .function getStats(id: string): Promise<{
totalPosts: number;
}>
getStats (id: string id )
return <JSX.IntrinsicElements.div: JSX.HtmlTag div >{const stats: {
totalPosts: number;
}
stats .totalPosts: number totalPosts } posts</JSX.IntrinsicElements.div: JSX.HtmlTag div >
}
const const stream: Readable stream = function renderToStream(html: JSX.Element | ((rid: number | string) => JSX.Element), rid?: number | string): ReadableTransforms a component tree who may contain Suspense components into a stream of
HTML.
@exampleimport { text} from 'node:stream/consumers';
// Prints out the rendered stream (2nd example shows with a custom id)
const stream = renderToStream(r => <AppWithSuspense rid={r} />)
const stream = renderToStream(<AppWithSuspense rid={myCustomId} />, myCustomId)
// You can consume it as a stream
for await (const html of stream) {
console.log(html.toString())
}
// Or join it all together (Wastes ALL Suspense benefits, but useful for testing)
console.log(await text(stream))
@paramhtml The component tree to render or a function that returns the component tree.@paramrid The request id to identify the request, if not provided, a new incrementing
id will be used.@see{@linkcode Suspense} renderToStream ((rid: string | number rid ) => (
<JSX.IntrinsicElements.html: JSX.HtmlHtmlTag html >
<JSX.IntrinsicElements.body: JSX.HtmlBodyTag body >
<JSX.IntrinsicElements.h1: JSX.HtmlTag h1 >Dashboard</JSX.IntrinsicElements.h1: JSX.HtmlTag h1 >
<function Suspense(props: SuspenseProps): JSX.ElementA component that returns a fallback while the async children are loading.
The rid prop is the one
{@linkcode
renderToStream
}
returns, this way the suspense
knows which request it belongs to.
Warning: Using Suspense without any type of runtime support will LEAK
memory and not work. Always use with renderToStream or within a framework that
supports it.
Suspense SuspenseProps.rid: string | numberThe request id is used to identify the request for this suspense.
rid ={rid: string | number rid } SuspenseProps.fallback: JSX.ElementThe fallback to render while the async children are loading.
fallback ={<JSX.IntrinsicElements.div: JSX.HtmlTag div >Loading stats...</JSX.IntrinsicElements.div: JSX.HtmlTag div >}>
<function UserStats({ id }: {
id: string;
}): Promise<string>
UserStats id: string id ="123" />
</function Suspense(props: SuspenseProps): JSX.ElementA component that returns a fallback while the async children are loading.
The rid prop is the one
{@linkcode
renderToStream
}
returns, this way the suspense
knows which request it belongs to.
Warning: Using Suspense without any type of runtime support will LEAK
memory and not work. Always use with renderToStream or within a framework that
supports it.
Suspense >
</JSX.IntrinsicElements.body: JSX.HtmlBodyTag body >
</JSX.IntrinsicElements.html: JSX.HtmlHtmlTag html >
))<html>
<body>
<h1>Dashboard</h1>
<div id="B:1:1" data-sf><div>Loading stats...</div></div>
</body>
</html>
<!-- SuspenseScript omitted for brevity -->
<template id="N:1:1" data-sr><div>123 posts</div></template>
<script id="S:1:1" data-ss>
$KITA_RC("1:1");
</script>The renderToStream callback receives the rid automatically. Pipe the returned stream
to your HTTP response.
import { text } from 'node:stream/consumers'
// As a stream
stream.pipe(response)
// Or consume entirely (loses streaming benefits, useful for testing)
const html = await text(stream)#Custom request IDs
If you need to control the request ID, pass it as the second argument and use it directly in your JSX.
const const stream: Readable stream = function renderToStream(html: JSX.Element | ((rid: number | string) => JSX.Element), rid?: number | string): ReadableTransforms a component tree who may contain Suspense components into a stream of
HTML.
@exampleimport { text} from 'node:stream/consumers';
// Prints out the rendered stream (2nd example shows with a custom id)
const stream = renderToStream(r => <AppWithSuspense rid={r} />)
const stream = renderToStream(<AppWithSuspense rid={myCustomId} />, myCustomId)
// You can consume it as a stream
for await (const html of stream) {
console.log(html.toString())
}
// Or join it all together (Wastes ALL Suspense benefits, but useful for testing)
console.log(await text(stream))
@paramhtml The component tree to render or a function that returns the component tree.@paramrid The request id to identify the request, if not provided, a new incrementing
id will be used.@see{@linkcode Suspense} renderToStream (
(rid: string | number rid ) => (
<function Suspense(props: SuspenseProps): JSX.ElementA component that returns a fallback while the async children are loading.
The rid prop is the one
{@linkcode
renderToStream
}
returns, this way the suspense
knows which request it belongs to.
Warning: Using Suspense without any type of runtime support will LEAK
memory and not work. Always use with renderToStream or within a framework that
supports it.
Suspense SuspenseProps.rid: string | numberThe request id is used to identify the request for this suspense.
rid ={rid: string | number rid } SuspenseProps.fallback: JSX.ElementThe fallback to render while the async children are loading.
fallback ={<JSX.IntrinsicElements.div: JSX.HtmlTag div >Loading...</JSX.IntrinsicElements.div: JSX.HtmlTag div >}>
<function AsyncContent(): Promise<string> AsyncContent />
</function Suspense(props: SuspenseProps): JSX.ElementA component that returns a fallback while the async children are loading.
The rid prop is the one
{@linkcode
renderToStream
}
returns, this way the suspense
knows which request it belongs to.
Warning: Using Suspense without any type of runtime support will LEAK
memory and not work. Always use with renderToStream or within a framework that
supports it.
Suspense >
),
const myCustomId: string myCustomId
)<div id="B:req-psyka8qt0hm:1" data-sf><div>Loading...</div></div>
<!-- SuspenseScript omitted for brevity -->
<template id="N:req-psyka8qt0hm:1" data-sr><div>Loaded content</div></template>
<script id="S:req-psyka8qt0hm:1" data-ss>
$KITA_RC("req-psyka8qt0hm:1");
</script>#Error handling
The catch prop handles errors thrown by async children. It accepts a JSX element or a
function that receives the error and returns JSX.
const const html: JSX.Element html = (
<function Suspense(props: SuspenseProps): JSX.ElementA component that returns a fallback while the async children are loading.
The rid prop is the one
{@linkcode
renderToStream
}
returns, this way the suspense
knows which request it belongs to.
Warning: Using Suspense without any type of runtime support will LEAK
memory and not work. Always use with renderToStream or within a framework that
supports it.
Suspense
SuspenseProps.rid: string | numberThe request id is used to identify the request for this suspense.
rid ={const rid: "req-1" rid }
SuspenseProps.fallback: JSX.ElementThe fallback to render while the async children are loading.
fallback ={<JSX.IntrinsicElements.div: JSX.HtmlTag div >Loading...</JSX.IntrinsicElements.div: JSX.HtmlTag div >}
SuspenseProps.catch?: JSX.Element | ((error: unknown) => JSX.Element) | undefinedThis error boundary is used to catch any error thrown by an async component and
streams its fallback instead.
Undefined behaviors happens on each browser kind when the html stream is unexpected closed by the server if an error is thrown. You should always define an error boundary to catch errors.
This does not catches for errors thrown by the suspense itself or async fallback
components. Please use
{@linkcode
ErrorBoundary
}
to catch them instead.
catch ={(error: unknown error ) => <JSX.IntrinsicElements.div: JSX.HtmlTag div >Failed: {var String: StringConstructor
(value?: any) => string
Allows manipulation and formatting of text strings and determination and location of substrings within strings.
String (error: unknown error )}</JSX.IntrinsicElements.div: JSX.HtmlTag div >}
>
<function AsyncContent(): Promise<string> AsyncContent />
</function Suspense(props: SuspenseProps): JSX.ElementA component that returns a fallback while the async children are loading.
The rid prop is the one
{@linkcode
renderToStream
}
returns, this way the suspense
knows which request it belongs to.
Warning: Using Suspense without any type of runtime support will LEAK
memory and not work. Always use with renderToStream or within a framework that
supports it.
Suspense >
)<div id="B:req-1:1" data-sf><div>Loading...</div></div>Without a catch prop, errors propagate to the stream as an 'error' event. Always
provide a catch handler in production to prevent the stream from closing unexpectedly.
#Multiple boundaries
Each Suspense boundary resolves independently. Place separate boundaries around sections that fetch different data so they can appear as soon as their data is ready.
const const stream: Readable stream = function renderToStream(html: JSX.Element | ((rid: number | string) => JSX.Element), rid?: number | string): ReadableTransforms a component tree who may contain Suspense components into a stream of
HTML.
@exampleimport { text} from 'node:stream/consumers';
// Prints out the rendered stream (2nd example shows with a custom id)
const stream = renderToStream(r => <AppWithSuspense rid={r} />)
const stream = renderToStream(<AppWithSuspense rid={myCustomId} />, myCustomId)
// You can consume it as a stream
for await (const html of stream) {
console.log(html.toString())
}
// Or join it all together (Wastes ALL Suspense benefits, but useful for testing)
console.log(await text(stream))
@paramhtml The component tree to render or a function that returns the component tree.@paramrid The request id to identify the request, if not provided, a new incrementing
id will be used.@see{@linkcode Suspense} renderToStream ((rid: string | number rid ) => (
<JSX.IntrinsicElements.html: JSX.HtmlHtmlTag html >
<JSX.IntrinsicElements.body: JSX.HtmlBodyTag body >
<function Suspense(props: SuspenseProps): JSX.ElementA component that returns a fallback while the async children are loading.
The rid prop is the one
{@linkcode
renderToStream
}
returns, this way the suspense
knows which request it belongs to.
Warning: Using Suspense without any type of runtime support will LEAK
memory and not work. Always use with renderToStream or within a framework that
supports it.
Suspense SuspenseProps.rid: string | numberThe request id is used to identify the request for this suspense.
rid ={rid: string | number rid } SuspenseProps.fallback: JSX.ElementThe fallback to render while the async children are loading.
fallback ={<JSX.IntrinsicElements.div: JSX.HtmlTag div >Loading user...</JSX.IntrinsicElements.div: JSX.HtmlTag div >}>
<function UserProfile({ id }: {
id: string;
}): Promise<string>
UserProfile id: string id ="123" />
</function Suspense(props: SuspenseProps): JSX.ElementA component that returns a fallback while the async children are loading.
The rid prop is the one
{@linkcode
renderToStream
}
returns, this way the suspense
knows which request it belongs to.
Warning: Using Suspense without any type of runtime support will LEAK
memory and not work. Always use with renderToStream or within a framework that
supports it.
Suspense >
<function Suspense(props: SuspenseProps): JSX.ElementA component that returns a fallback while the async children are loading.
The rid prop is the one
{@linkcode
renderToStream
}
returns, this way the suspense
knows which request it belongs to.
Warning: Using Suspense without any type of runtime support will LEAK
memory and not work. Always use with renderToStream or within a framework that
supports it.
Suspense SuspenseProps.rid: string | numberThe request id is used to identify the request for this suspense.
rid ={rid: string | number rid } SuspenseProps.fallback: JSX.ElementThe fallback to render while the async children are loading.
fallback ={<JSX.IntrinsicElements.div: JSX.HtmlTag div >Loading feed...</JSX.IntrinsicElements.div: JSX.HtmlTag div >}>
<function ActivityFeed({ id }: {
id: string;
}): Promise<string>
ActivityFeed id: string id ="123" />
</function Suspense(props: SuspenseProps): JSX.ElementA component that returns a fallback while the async children are loading.
The rid prop is the one
{@linkcode
renderToStream
}
returns, this way the suspense
knows which request it belongs to.
Warning: Using Suspense without any type of runtime support will LEAK
memory and not work. Always use with renderToStream or within a framework that
supports it.
Suspense >
</JSX.IntrinsicElements.body: JSX.HtmlBodyTag body >
</JSX.IntrinsicElements.html: JSX.HtmlHtmlTag html >
))<html>
<body>
<div id="B:2:1" data-sf><div>Loading user...</div></div>
<div id="B:2:2" data-sf><div>Loading feed...</div></div>
</body>
</html>
<!-- SuspenseScript omitted for brevity -->
<template id="N:2:1" data-sr><div>User 123</div></template>
<script id="S:2:1" data-ss>
$KITA_RC("2:1");
</script>
<template id="N:2:2" data-sr><div>Feed 123</div></template>
<script id="S:2:2" data-ss>
$KITA_RC("2:2");
</script>The same rid is shared across all Suspense boundaries in the same request. The stream
closes automatically when the last boundary resolves.
#Async fallbacks
The fallback prop can be an async component, but this blocks the entire stream until the
fallback resolves. The server will not send any HTML until the async fallback is ready.
async function function LoadingMessage(): Promise<string> LoadingMessage () {
await function loadTranslations(): Promise<void> loadTranslations ()
return <JSX.IntrinsicElements.div: JSX.HtmlTag div >Loading user data...</JSX.IntrinsicElements.div: JSX.HtmlTag div >
}
const const stream: Readable stream = function renderToStream(html: JSX.Element | ((rid: number | string) => JSX.Element), rid?: number | string): ReadableTransforms a component tree who may contain Suspense components into a stream of
HTML.
@exampleimport { text} from 'node:stream/consumers';
// Prints out the rendered stream (2nd example shows with a custom id)
const stream = renderToStream(r => <AppWithSuspense rid={r} />)
const stream = renderToStream(<AppWithSuspense rid={myCustomId} />, myCustomId)
// You can consume it as a stream
for await (const html of stream) {
console.log(html.toString())
}
// Or join it all together (Wastes ALL Suspense benefits, but useful for testing)
console.log(await text(stream))
@paramhtml The component tree to render or a function that returns the component tree.@paramrid The request id to identify the request, if not provided, a new incrementing
id will be used.@see{@linkcode Suspense} renderToStream ((rid: string | number rid ) => (
<function Suspense(props: SuspenseProps): JSX.ElementA component that returns a fallback while the async children are loading.
The rid prop is the one
{@linkcode
renderToStream
}
returns, this way the suspense
knows which request it belongs to.
Warning: Using Suspense without any type of runtime support will LEAK
memory and not work. Always use with renderToStream or within a framework that
supports it.
Suspense SuspenseProps.rid: string | numberThe request id is used to identify the request for this suspense.
rid ={rid: string | number rid } SuspenseProps.fallback: JSX.ElementThe fallback to render while the async children are loading.
fallback ={<function LoadingMessage(): Promise<string> LoadingMessage />}>
<function UserProfile({ id }: {
id: string;
}): Promise<string>
UserProfile id: string id ="123" />
</function Suspense(props: SuspenseProps): JSX.ElementA component that returns a fallback while the async children are loading.
The rid prop is the one
{@linkcode
renderToStream
}
returns, this way the suspense
knows which request it belongs to.
Warning: Using Suspense without any type of runtime support will LEAK
memory and not work. Always use with renderToStream or within a framework that
supports it.
Suspense >
))<div id="B:3:1" data-sf><div>Loading user data...</div></div>
<!-- SuspenseScript omitted for brevity -->
<template id="N:3:1" data-sr><div>User 123</div></template>
<script id="S:3:1" data-ss>
$KITA_RC("3:1");
</script>The stream waits for LoadingMessage to resolve before sending anything to the client.
This defeats the purpose of streaming. Use synchronous fallbacks whenever possible.
If an async fallback throws an error, wrap the Suspense in an ErrorBoundary to handle
it. The catch prop only handles errors from the async children, not from the fallback
itself.