Skip to main content
doc0blade47/honodocumentation home
Sign in

JSX Components and Context

Last updated Oct 12, 2026100% grounded
Relevant source files

The following files were used as context for generating this wiki page:

Overview

Hono's JSX implementation bridges server-side rendering, streaming, and client DOM interaction JSX Runtime, Client DOM and Hooks, JSX Streaming and Suspense. It provides robust APIs for component hierarchy management, context sharing, document head control, and middleware-driven layout rendering across modern edge and browser runtimes Execution Context.

Sources: src/jsx/context.ts:8-50, src/middleware/jsx-renderer/index.ts:15-129, src/jsx/dom/intrinsic-element/components.ts:257-315

Context Creation and Propagation Across Runtimes

API Mechanics and Runtime Differentiation

Hono manages context sharing through createContext, Context.Provider, and useContext, implemented separately for server string rendering and client-side DOM reconciliation. The Context<T> interface extends component types with a values array holding active context states and a Provider property pointing to the context itself. When initialized via createContext<T>(defaultValue: T), it registers the context instance in the globalContexts array.

Sources: src/jsx/context.ts:8-14, src/jsx/context.ts:41-47, src/jsx/dom/context.ts:45-51

Runtime EnvironmentImplementation TargetProvider MechanismError / Cleanup Handling
Server (src/jsx/context.ts)FC<PropsWithChildren<{ value: T }>>Pushes to values, stringifies children, pops in finally / catch.try...catch pops value on synchronous exception; .finally() pops on promises.
Client DOM (src/jsx/dom/context.ts)Function (createContextProviderFunction)Injects internal tag functions before and after child nodes to push/pop values.DOM_ERROR_HANDLER pops value and rethrows error during DOM reconciliation.

Sources: src/jsx/context.ts:17-40, src/jsx/dom/context.ts:7-43

Note

In the server runtime, asynchronous children returning a Promise keep the context value active until settled. The .finally() handler ensures values.pop() executes whether the promise resolves or rejects, preventing stack corruption across concurrent requests.

Sources: src/jsx/context.ts:32-35

Consumption and Value Resolution

Consuming context values relies on useContext, which inspects the last element of the context's values stack via .at(-1). Because nested providers push new entries onto the values array, useContext naturally retrieves the innermost provider's value.

export const useContext = <T>(context: Context<T>): T => {
  return context.values.at(-1) as T
}

Sources: src/jsx/context.ts:52-54

Warning

Modifying or mutating the values array directly outside of provider lifecycle execution corrupts context resolution for subsequent renders, especially in shared server runtimes where globalContexts retains references across requests.

Sources: src/jsx/context.ts:13-16

Error Handling with ErrorBoundary

Error Capture and Fallback Resolution

Hono provides ErrorBoundary components to intercept errors during server-side rendering and client-side DOM reconciliationJSX Runtime, Client DOM and Hooks. On the server, ErrorBoundary accepts fallback, fallbackRender, and onError properties, mapping child components while monitoring for exceptions during early resolution and promise settlement src/jsx/components.ts:52-58. When an error is thrown, the component invokes resolveFallbackStr to process string or asynchronous fallback content src/jsx/components.ts:70-81.

const resolveFallbackStr = async () => {
  const awaitedFallback = await fallback
  if (typeof awaitedFallback === 'string') {
    fallbackStr = awaitedFallback
  } else {
    fallbackStr = await awaitedFallback?.toString()
    if (typeof fallbackStr === 'string') {
      // should not apply `raw` if fallbackStr is undefined, null, or boolean
      fallbackStr = raw(fallbackStr)
    }
  }
}

Sources: src/jsx/components.ts:70-81

The server execution call-chain for fallback text processing proceeds through specific internal helpers:

  1. ErrorBoundary intercepts the component error src/jsx/components.ts:92-98.
  2. resolveFallbackStr awaits the fallback value and prepares its string representation src/jsx/components.ts:70-81.
  3. raw wraps the resulting string with HTML-escaped flags when valid src/jsx/components.ts:78.

Sources: src/jsx/components.ts:52-81, src/utils/html.ts:40-46

Note

If a fallback prop is a promise or contains asynchronous children, ErrorBoundary defers rendering error templates until resolution fails, injecting script targets (data-hono-target) to swap placeholder templates on the client.

Sources: src/jsx/components.ts:102-128

Client DOM Error Boundaries

In browser environments, the DOM implementation of ErrorBoundary attaches an error handler symbol (DOM_ERROR_HANDLER) to rendered fragment nodes src/jsx/dom/components.ts:7-23.

> = (({ children, fallback, fallbackRender, onError }: any) => {
  const res = Fragment({ children })
  ;(res as any)[DOM_ERROR_HANDLER] = (err: any) => {
    if (err instanceof Promise) {
      throw err
    }
    onError?.(err)
    return fallbackRender?.(err) || fallback
  }
  return res
}) as any

Sources: src/jsx/dom/components.ts:13-23

Symbol ConstantFile OriginTarget Purpose
DOM_RENDERERsrc/jsx/constants.ts:1Identifies primary node render functions.
DOM_ERROR_HANDLERsrc/jsx/constants.ts:2Assigns catch handlers to fragments for error recovery.
DOM_STASHsrc/jsx/constants.ts:3Stores hook state arrays for client components.
DOM_INTERNAL_TAGsrc/jsx/constants.ts:4Marks internal wrapper elements during reconciliation.

Sources: src/jsx/constants.ts:1-4

Warning

Non-promise errors thrown inside client DOM ErrorBoundary nodes invoke onError and immediately return fallbackRender or fallback, whereas promise rejections are rethrown to trigger suspense fallback handling src/jsx/dom/components.ts:15-21.

Sources: src/jsx/dom/components.ts:15-21

Document Metadata and Intrinsic Component Hoisting

Deduplication Keys and Strategy

Hono manages document metadata elements—such as title, meta, link, script, and style—through specialized component wrappers that support server-side hoisting and deduplication src/jsx/intrinsic-element/common.ts:3-9. The deduplication configuration relies on mapping intrinsic tag names to keys used for uniqueness checks during stream serialization src/jsx/intrinsic-element/common.ts:3-9.

export const deDupeKeyMap: Record<string, string[]> = {
  title: [],
  script: ['src'],
  style: ['data-href'],
  link: ['href'],
  meta: ['name', 'httpEquiv', 'charset', 'itemProp'],
}

Sources: src/jsx/intrinsic-element/common.ts:3-9

Intrinsic TagDeduplication Key(s)Sorting Behavior
title[] (none)Unsorted
script['src']Unsorted
style['data-href']Sorted via data-precedence
link['href']Sorted if stylesheet with precedence
meta['name', 'httpEquiv', 'charset', 'itemProp']Unsorted

Sources: src/jsx/intrinsic-element/common.ts:3-9, src/jsx/intrinsic-element/components.ts:101-105

Note

If a meta tag includes an itemProp attribute, it bypasses hoisting logic entirely and renders immediately without deduplication src/jsx/intrinsic-element/components.ts:97-99.

Sources: src/jsx/intrinsic-element/components.ts:97-99

Server-Side Hoisting and Stream Injection

During server rendering, documentMetadataTag intercepts metadata nodes, generates their string representations, and attaches an HtmlEscapedCallback via insertIntoHead src/jsx/intrinsic-element/components.ts:96-119. When the HTML stream buffer encounters the </head> closing tag, accumulated metadata items are deduplicated against recorded maps and injected directly before the tag src/jsx/intrinsic-element/components.ts:21-91.

let duped = false
const deDupeKeys = deDupeKeyMap[tagName]
const deDupeByKey = shouldDeDupeByKey(tagName, precedence !== undefined)
if (deDupeByKey) {
  LOOP: for (const [, tagProps] of tags) {
    if (
      tagName === 'link' &&
      !(tagProps.rel === 'stylesheet' && tagProps[dataPrecedenceAttr] !== undefined)
    ) {
      continue
    }
    for (const key of deDupeKeys) {
      if ((tagProps?.[key] ?? null) === props?.[key]) {
        duped = true
        break LOOP
      }
    }
  }
}

Sources: src/jsx/intrinsic-element/components.ts:36-54

Stylesheets with precedence attributes utilize dataPrecedenceAttr (data-precedence) to order style and link elements within the head block based on unique precedence strings src/jsx/intrinsic-element/common.ts:13-16, src/jsx/intrinsic-element/components.ts:66-81.

Warning

Link elements lacking a stylesheet relationship, or containing explicit onLoad or onError handlers, skip server-side head hoisting and fall back to standard inline rendering src/jsx/intrinsic-element/components.ts:163-168.

Sources: src/jsx/intrinsic-element/components.ts:163-168

Component Tree Rendering with JSX Renderer Middleware

Setup and Configuration

The jsxRenderer middleware intercepts Hono requests to inject layout wrappers, provide a request context via RequestContext, and configure response serialization src/middleware/jsx-renderer/index.ts:116-129. It accepts an optional component layout function and an options object or factory callback defining docType and stream behavior src/middleware/jsx-renderer/index.ts:33-38.

type RendererOptions = {
  docType?: boolean | string
  stream?: boolean | Record<string, string>
}

Sources: src/middleware/jsx-renderer/index.ts:18-21

Option KeyTypeDefault ValuePurpose
docTypeboolean | stringtrue (<!DOCTYPE html>)Prepends a document type string or suppresses it when set to false [[src/middleware/jsx-renderer/index.ts:19`
streamboolean | Record<string, string>falseEnables chunked HTML streaming response or custom stream headers [[src/middleware/jsx-renderer/index.ts:20`

Sources: src/middleware/jsx-renderer/index.ts:18-21, src/middleware/jsx-renderer/index.ts:42-47, src/middleware/jsx-renderer/index.ts:66-75

Note

When options.stream is set to true, the middleware automatically sets standard headers including Transfer-Encoding: chunked, Content-Type: text/html; charset=UTF-8, and Content-Encoding: Identity src/middleware/jsx-renderer/index.ts:67-70.

Sources: src/middleware/jsx-renderer/index.ts:67-70

Call-Chain Execution Walkthrough

When c.render() is invoked during request handling, execution flows through the renderer generation phase down to template evaluation and callback resolution.

  1. jsxRenderer — Registers the layout wrapper and assigns the renderer function generated by createRenderer to the context src/middleware/jsx-renderer/index.ts:116-129. Sources: src/middleware/jsx-renderer/index.ts:116-129
  2. createRenderer — Resolves options, wraps the layout component with user children inside a RequestContext.Provider, and delegates to either renderToReadableStream or c.html src/middleware/jsx-renderer/index.ts:33-80. Sources: src/middleware/jsx-renderer/index.ts:33-80
  3. html — Parses template strings and values into a buffer, handling raw injection and deferred callbacks src/helper/html/index.ts:11-56. Sources: src/helper/html/index.ts:11-56
  4. resolveCallbackSync — Iterates through any registered HtmlEscapedCallback functions at the stringify phase to finalize output buffer content src/utils/html.ts:129-140. Sources: src/utils/html.ts:129-140

Sources: src/middleware/jsx-renderer/index.ts:33-129, src/helper/html/index.ts:11-56, src/utils/html.ts:129-140

Warning

Attempting to call useRequestContext() outside of a tree wrapped by RequestContext throws an error stating that RequestContext is not provided src/middleware/jsx-renderer/index.ts:155-164.

Sources: src/middleware/jsx-renderer/index.ts:155-164

Form Handling and Action Integration

Client-Side Form Action Dispatch

Client-side form handling supports action functions by wrapping intrinsic form elements with a FormContext provider and intercepting submit events. When a submit event occurs on a trusted event, the handler extracts form data using FormData, updates the state with pending data and dirtiness flags, and executes the provided action function. If the action function returns a promise, it registers the action and awaits its completion before resetting the form state.

const [state, setState] = useState<[FormData | null, boolean]>([null, false])
const onSubmit = useCallback<(ev: SubmitEvent | CustomEvent) => void>(
  async (ev: SubmitEvent | CustomEvent) => {
    const currentAction = ev.isTrusted
      ? action
      : (ev as CustomEvent).detail[customEventFormAction]
    if (typeof currentAction !== 'function') {
      return
    }
 
    ev.preventDefault()
    const formData = new FormData(ev.target as HTMLFormElement)
    setState([formData, true])
    const actionRes = currentAction(formData)
    if (actionRes instanceof Promise) {
      registerAction(actionRes)
      await actionRes
    }
    setState([null, true])
  },
  []
)

Sources: src/jsx/dom/intrinsic-element/components.ts:332-353

Note

Form-actionable elements such as submit buttons intercept click events when a formAction function is supplied, preventing default navigation and dispatching a custom submit event carrying the action reference via customEventFormAction. Sources: src/jsx/dom/intrinsic-element/components.ts:388-405

On the server side, intrinsic form components inspect the action property to verify if it is a function. If the action function contains a PERMALINK symbol attached to it, the action is transformed into its corresponding string permalink route for static or server-rendered markup generation.

export const form: FC<
  PropsWithChildren<{
    action?: Function | string
    method?: 'get' | 'post'
  }>
> = (props) => {
  if (typeof props.action === 'function') {
    props.action = PERMALINK in props.action ? (props.action[PERMALINK] as string) : undefined
  }

Sources: src/jsx/intrinsic-element/components.ts:182-190

Warning

Passing a function as a form action without an associated PERMALINK symbol during server-side rendering results in an undefined action attribute value. Sources: src/jsx/intrinsic-element/components.ts:188-190

Was this page helpful?