Rendering
render(App, element) mounts a spec (or a vnode) into a DOM node. Calling it again on the same node patches instead of wiping.
renderToString(App) walks the tree without a document. Event handlers are omitted, so use hydrate(App, element) in the browser to attach them while reusing the prerendered DOM.
Render the same component tree and initial props on the server and in the browser. Hydration keeps matching DOM nodes, attaches events and refs, and patches text or elements that differ. On an empty element it behaves like render.
// build/server
const markup = renderToString(<App initialUrl="/docs/state/" />);
// browser
hydrate(<App initialUrl={location.pathname} />, document.getElementById("app"));
Updates:
setStaterecords the next state (cloned and frozen in DEV)- The instance is marked dirty and queued
- A microtask flushes the queue
render()produces a new vnode — children with the same props are skipped- A keyed diff patches the previous tree. Same-order keyed children patch in place; fragments move as the contiguous range between their marker comments
this.observe(store) marks the instance dirty when the store changes, without a dummy setState.
DOM writes follow the tree, not the other way around. Unchanged style keys are not rewritten. A controlled value that already matches the input is left alone so the caret stays put. SVG attributes such as points still go through setAttribute — the animated SVGPointList property is not a string.
Forms mean the same thing on the server and after hydration:
textarea valueis serialized as text content, not avalueattributeselect valuemarks the matchingoptionasselected- hydration writes live properties, not just HTML attributes
- user input typed before hydration is overwritten by a controlled value
select multipleis unsupported- if both
dangerouslySetInnerHTMLand children are set, innerHTML wins (development warns)
Each html evaluation that is mounted gets its own DOM and instance, even when HTM reuses a static template.
The 2.x renderer assigned node.innerHTML = "" on every update. That is gone on purpose.
flushSync(fn) runs fn and flushes immediately. The site router uses it so view transitions see the new DOM.
Recovering from errors
A failed patch can have already changed the DOM. SvenJS cleans up both the previous tree and the partially rendered tree, including refs and subscriptions, before reporting the error. It does not roll back DOM changes or component state.
- If
render()orhydrate()fails while writing a root, that root is cleared. Callrender()again to mount a fresh tree. Failed hydration also clears any remaining server HTML. - If a scheduled component update fails while patching its output, that output is
replaced with an empty placeholder. The owning component and its state remain,
so a later
setState()can render again in the same position. Other components queued for updates still run. - If the component’s own
render()throws before patching starts, its previous output remains during a scheduled update.
Partially mounted components receive cleanup but no pending onMount call.
Successful renders into independent roots keep their mount hooks. Cleanup errors
are reported without replacing the original render error. An exception from
onMount happens after commit and does not undo the completed DOM update.