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

  1. Use renderToReadableStream (or another async renderer) for trees containing async components
  2. Make the component synchronous: await the data outside and pass it in as props
  3. 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

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


AI-assisted analysis of honojs/hono@e2740d5a1b (2026-08-28). Data as JSON: /api/errors/8113de65e8c62a68. Report an issue: GitHub.