Orientation Arc
Routing Algorithms
Rendering and JSX
Runtime Adapters
Middleware Ecosystem
Developer Helpers
System Utilities
The following files were used as context for generating this wiki page:
DOM Rendering is a lightweight, runtime-focused engine within the Hono framework designed to bridge the gap between declarative JSX templates and imperative browser DOM manipulations. Unlike server-side rendering (SSR) implementations that serialize JSX into HTML strings, this subsystem maintains an internal virtual-tree representation of nodes, enabling efficient updates, hook management, and dynamic interaction within the client-side environment. It is the core mechanism that allows Hono applications to remain responsive without the overhead of heavy virtual DOM reconciliation found in traditional libraries.
The architecture centers around the Node structure—a unified interface that distinguishes between static text nodes and interactive element nodes. By tracking previous props (pP) and virtual children (vC), the renderer minimizes browser reflows through surgical DOM updates. It supports React-like hooks such as useState, useEffect, and useMemo, which are integrated directly into the lifecycle of each node object, allowing state-driven UI updates to occur precisely where needed without traversing the entire component tree.
Interaction with the system is typically handled through the render() or hydrateRoot() APIs, which initiate the transformation of a JSX structure into a live DOM tree. Once rendered, the renderer automatically manages effect cleanup, event binding, and context propagation, ensuring that declarative updates result in predictable DOM transitions. This design prioritizes runtime performance, using native browser capabilities and minimal overhead to handle the complexities of component-based UI development.
The core of the DOM renderer is the NodeObject type, a recursive data structure representing an element in the DOM tree. Unlike a simple HTML string, the NodeObject caches runtime metadata required for reconciliation and reactive updates.
Sources: src/jsx/dom/render.ts:41-65
The renderer process uses a two-phase cycle: build (determining the structure) and apply (syncing that structure to the live DOM). The build function recursively transforms JSX nodes into the Node internal representation.
When a node requires an update, the renderer uses a WeakMap (updateMap) to consolidate pending changes, ensuring that rapid state updates trigger only the necessary re-rendering. During the build phase, the system maintains a buildDataStack to track the context and the current node being processed, which is essential for useContext and hook initialization.
Sources: src/jsx/dom/render.ts:497-665, src/jsx/dom/render.ts:740-781
Hooks are indexed within the [DOM_STASH] field of a NodeObject. The system utilizes a current hook index to ensure that consecutive calls to hooks within a component remain synchronized across re-renders.
Note
useEffect callbacks are scheduled via requestAnimationFrame to decouple DOM mutations from effect execution, while useLayoutEffect runs synchronously after the DOM is updated but before the browser paints.
The system ensures correct cleanup via the removeNode function, which explicitly triggers registered effect cleanups and clears references stored in the refCleanupMap.
Sources: src/jsx/dom/render.ts:341-362, src/jsx/hooks/index.ts:260-287
The renderer optimizes property application by distinguishing between event listeners, special attributes (like dangerouslySetInnerHTML), and standard DOM attributes.
onClick) are parsed into [eventName, capture] pairs. The system maintains an eventCache for frequently used events to speed up lookups.SELECT and INPUT fields, where applySelectValue or direct property assignment is used instead of standard setAttribute to reflect internal states like selectedIndex or checked.container.setAttribute. The renderer uses a try-catch block around setAttribute specifically to catch InvalidCharacterError, avoiding a slow upfront regex validation of every attribute name.Sources: src/jsx/dom/render.ts:115-132, src/jsx/dom/render.ts:164-272
The DOM renderer integrates with ErrorBoundary components by maintaining an error stack in the Context. When a component throws an error, the renderer looks for the closest DOM_ERROR_HANDLER in the hierarchy.
If an error boundary is found, the system wraps the fallback in an update queue. This allows the boundary to recover gracefully. The renderer uses a cancelBuild symbol to halt the reconciliation process for subtrees that have crashed, preventing them from contaminating the global DOM state until a recovery render is triggered.
Sources: src/jsx/dom/render.ts:612-657
To render an application, use the createRoot API. This pattern creates a consistent root-level controller that can manage subsequent updates via useState.
import { createRoot } from 'hono/jsx/dom/client';
const App = ({ name }: { name: string }) => <h1>Hello {name}</h1>;
const root = createRoot(document.getElementById('root')!);
root.render(<App name="Hono" />);
// Later, trigger an update if the component was setup to be reactiveSources: src/jsx/dom/client.ts:23-66