honojs/hono · error · Error
Async component is not supported in renderToString
Error message
Async component is not supported in renderToString
What it means
hono/jsx/dom/server's renderToString calls element.toString(); if that returns a Promise (or other non-string), it means the element tree contains an async component or unawaited async expression, which synchronous string rendering cannot fulfill.
Source
Thrown at src/jsx/dom/server.ts:27
import version from './'
export interface RenderToStringOptions {
identifierPrefix?: string
}
/**
* Render JSX element to string.
* @param element JSX element to render.
* @param options Options for rendering.
* @returns Rendered string.
*/
const renderToString = (element: Child, options: RenderToStringOptions = {}): string => {
if (Object.keys(options).length > 0) {
console.warn('options are not supported yet')
}
const res = element?.toString() ?? ''
if (typeof res !== 'string') {
throw new Error('Async component is not supported in renderToString')
}
return res
}
export interface RenderToReadableStreamOptions {
identifierPrefix?: string
namespaceURI?: string
nonce?: string
bootstrapScriptContent?: string
bootstrapScripts?: string[]
bootstrapModules?: string[]
progressiveChunkSize?: number
signal?: AbortSignal
onError?: (error: unknown) => string | void
}
/**
* Render JSX element to readable stream.View on GitHub (pinned to e2740d5a1b)
Solutions
- Use renderToReadableStream (or another async renderer) for trees containing async components
- Make the component synchronous: await the data outside and pass it in as props
- Replace async components in the sync tree with sync ones, or split async subtrees into the streaming path
Example fix
// before
const AsyncPage = async () => {
const data = await getData()
return <div>{data}</div>
}
const html = renderToString(<AsyncPage />) // throws
// after
const data = await getData()
const html = renderToString(<Page data={data} />) Defensive patterns
Strategy: type-guard
Validate before calling
const isSyncRenderable = (el: Child): boolean => {
const s = (el as { toString?: () => unknown })?.toString?.()
return typeof s === 'string'
} Type guard
const isAsyncComponent = (fn: unknown): boolean => typeof fn === 'function' && fn.constructor?.name === 'AsyncFunction'
Try / catch
null
Prevention
- Use renderToReadableStream for any tree with async components
- Fetch data before render and pass results as props in sync paths
- Audit for async function components before switching to renderToString
When it happens
Trigger: Rendering a JSX tree that includes an async function component or an element whose toString resolves to a Promise, e.g. `renderToString(<AsyncPage />)` where AsyncPage is `async () => {...}`; passing a Promise child or memoized async fragment.
Common situations: Switching from hono's stream renderer (renderToReadableStream, which supports async) to renderToString without converting components; introducing data-fetching (await prisma..., await fetch...) into components used in the sync path; refactoring a page component to async during SSR setup.
Related errors
- Can only set one of `children` or `props.dangerouslySetInner
- Invalid prop '${key}' of type 'function' supplied to '${tag}
- style sheet not found
- getServerSnapshot is required for server side rendering
- Invalid JSX tag name: ${tag}
AI-assisted analysis of honojs/hono@e2740d5a1b (2026-08-28).
Data as JSON: /api/errors/8113de65e8c62a68.
Report an issue: GitHub.