Relevant source files
The following files were used as context for generating this wiki page:
- src/middleware/jsx-renderer/index.ts
- src/jsx/dom/intrinsic-element/components.ts
- src/jsx/intrinsic-element/components.ts
- src/jsx/components.ts
- src/jsx/dom/render.ts
- src/jsx/base.ts
- src/jsx/context.ts
- src/jsx/jsx-runtime.ts
- src/jsx/streaming.ts
- src/jsx/hooks/index.ts
- src/jsx/intrinsic-elements.ts
- src/jsx/dom/jsx-dev-runtime.ts
- src/jsx/jsx-dev-runtime.ts
- src/context.ts
- src/jsx/dom/context.ts
- src/jsx/dom/hooks/index.ts
- src/jsx/dom/components.ts
- src/jsx/index.ts
- src/jsx/dom/index.ts
- src/jsx/dom/client.ts
- src/jsx/dom/css.ts
- src/jsx/dom/jsx-runtime.ts
- src/jsx/intrinsic-element/common.ts
- src/jsx/constants.ts
- src/jsx/dom/utils.ts
- package.json
- src/jsx/types.ts
- src/helper/html/index.ts
- src/utils/html.ts
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 Environment | Implementation Target | Provider Mechanism | Error / 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:
ErrorBoundaryintercepts the component error src/jsx/components.ts:92-98.resolveFallbackStrawaits the fallback value and prepares its string representation src/jsx/components.ts:70-81.rawwraps 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 anySources: src/jsx/dom/components.ts:13-23
| Symbol Constant | File Origin | Target Purpose |
|---|---|---|
DOM_RENDERER | src/jsx/constants.ts:1 | Identifies primary node render functions. |
DOM_ERROR_HANDLER | src/jsx/constants.ts:2 | Assigns catch handlers to fragments for error recovery. |
DOM_STASH | src/jsx/constants.ts:3 | Stores hook state arrays for client components. |
DOM_INTERNAL_TAG | src/jsx/constants.ts:4 | Marks 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 Tag | Deduplication 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 Key | Type | Default Value | Purpose |
|---|---|---|---|
docType | boolean | string | true (<!DOCTYPE html>) | Prepends a document type string or suppresses it when set to false [[src/middleware/jsx-renderer/index.ts:19` |
stream | boolean | Record<string, string> | false | Enables 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.
jsxRenderer— Registers the layout wrapper and assigns the renderer function generated bycreateRendererto the context src/middleware/jsx-renderer/index.ts:116-129. Sources: src/middleware/jsx-renderer/index.ts:116-129createRenderer— Resolves options, wraps the layout component with user children inside aRequestContext.Provider, and delegates to eitherrenderToReadableStreamorc.htmlsrc/middleware/jsx-renderer/index.ts:33-80. Sources: src/middleware/jsx-renderer/index.ts:33-80html— 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-56resolveCallbackSync— Iterates through any registeredHtmlEscapedCallbackfunctions 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
Server-Side Permalink Resolution
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
Related
On This Page
OverviewContext Creation and Propagation Across RuntimesAPI Mechanics and Runtime DifferentiationConsumption and Value ResolutionError Handling with ErrorBoundaryError Capture and Fallback ResolutionClient DOM Error BoundariesDocument Metadata and Intrinsic Component HoistingDeduplication Keys and StrategyServer-Side Hoisting and Stream InjectionComponent Tree Rendering with JSX Renderer MiddlewareSetup and ConfigurationCall-Chain Execution WalkthroughForm Handling and Action IntegrationClient-Side Form Action DispatchServer-Side Permalink ResolutionRelated