Coherent.js API Reference
A reference for the most used public APIs. Each package's README lists its full exports; the TypeScript declarations in packages/*/types are checked against the runtime exports in CI.
Supported Imports
Use only the package entry points and their documented subpaths:
import { render } from '@coherent.js/core';
import { hydrate } from '@coherent.js/client';
import { createRouter } from '@coherent.js/api';Core Rendering
render(component, options?)
Renders a component tree to an HTML string. It is synchronous: await data and async components before calling it (a Promise anywhere in the tree throws Cannot render a Promise at <path>).
import { render } from '@coherent.js/core';
const html = render({
div: {
className: 'greeting',
children: [{ h1: { text: 'Hello, World!' } }]
}
});
// <div class="greeting"><h1>Hello, World!</h1></div>Options:
| Option | Default | |
|---|---|---|
onError |
— | (error, { path }) => replacement — called when a function component throws; its return value is rendered instead (null omits the component). Without it the error propagates out of render() as a RenderingError with the component's path and the original error as cause. |
enableCache |
false |
Cache whole renders, keyed on the complete tree. Trees containing functions, class instances or Dates are never cached. |
cache |
shared cache | A cache from createCacheManager({ maxCacheSize, ttlMs }) to use instead of the shared one |
cacheTTL |
300000 | TTL in ms for entries this render adds |
scoped / encapsulate |
false |
Scope the component's <style> rules (see Styling) |
minify |
false |
Minify the output |
maxDepth |
100 | Maximum tree depth |
enableMonitoring |
false |
Record timings in performanceMonitor |
cacheSize is deprecated and ignored; pass cache: createCacheManager({ maxCacheSize }) instead.
Rendering rules worth knowing:
textand attribute values are HTML-escaped. Raw markup only goes throughhtml:ordangerouslySetInnerContent().null,undefinedand booleans inchildrenrender nothing, socond && { li: ... }works.text: falseprintsfalse.className(orclass) accepts a string, an array (['btn', active && 'btn--active']) or an object ({ active: isActive }).- Function-valued
on*props (onClick: () => ...) render nothing on the server;hydrate()attaches them in the browser. String handlers (onclick: 'history.back()') are rendered as attributes. - An attribute name containing whitespace, quotes,
<,>,/,=or control characters makesrender()throw.data-*,aria-*,x-on:click,@click,:classandxlink:hrefare fine. - A multi-key object (
{ h1: ..., p: ... }) renders each key as a sibling.
renderToStream(component, options?)
An async generator of HTML chunks whose concatenation is exactly render()'s output. The event loop gets a turn after every chunk. Accepts render()'s options (except the cache ones and minify) plus chunkSize (default 8192 characters).
import { Readable } from 'node:stream';
import { renderToStream, streamingUtils } from '@coherent.js/core';
Readable.from(renderToStream(Page())).pipe(res);
// or: writes with backpressure and aborts the response if rendering fails
await streamingUtils.streamToResponse(renderToStream(Page()), res);Errors reject the iteration (they are not written into the HTML).
renderWithTemplate(component, { template })
Renders a component and inserts it into a template at {{content}}; used by the framework adapters.
import { renderWithTemplate } from '@coherent.js/core';
const page = renderWithTemplate(App(), { template: '<!DOCTYPE html>\n{{content}}' });Components
A component is a function returning a node. You call it with its props (Card({ title })); a function placed directly in a tree is called by the renderer with no arguments.
memo(component, keyFnOrOptions?)
Caches a component's output per props. Every memoized component has its own bounded LRU cache.
import { memo } from '@coherent.js/core';
const ProductCard = memo(
({ product }) => ({ article: { text: product.name } }),
{ keyFn: ({ product }) => `${product.id}:${product.updatedAt}`, maxSize: 500 }
);
// memo(fn, keyFn) works too
const Row = memo(({ item }) => ({ li: { text: item.name } }), ({ item }) => item.id);Options: keyFn, maxSize (default 100), strategy ('lru', 'ttl', 'weak', 'simple'), ttl, stats, onHit, onMiss, onEvict.
createComponent(renderFunction | definition)
Wraps a render function (or a { name, render, state, methods, ... } definition) in a callable component instance with mount, update and destroy:
import { createComponent } from '@coherent.js/core';
const Greeting = createComponent(({ name }) => ({ h1: { text: `Hello, ${name}!` } }));
render(Greeting({ name: 'Ada' })); // <h1>Hello, Ada!</h1>The instance and its state are shared by everyone who renders it. On the server, pass per-request data through props.
withState(initialState, options?)
A higher-order component that injects state and stateUtils props:
import { withState } from '@coherent.js/core';
const Counter = withState({ count: 0 })(({ state, stateUtils }) => ({
div: {
children: [
{ p: { text: `Count: ${state.count}` } },
{ button: { text: 'Increment', onclick: () => stateUtils.setState({ count: state.count + 1 }) } }
]
}
}));stateUtils:setState,getState,resetState,updateState(fn),batchUpdate,computed,subscribe,unsubscribe.- Options include
debug,validator,middleware,reducer,actions,persistent/storageKey,onStateChange,onMount. - The state container is created once per
withState(...)(Component)call, so on the server it is shared by every request. Keep request data in props, and usewithStatefor browser-side state.
Error boundaries
import { createErrorBoundary } from '@coherent.js/core';
const boundary = createErrorBoundary({
fallback: { p: { text: 'This widget is unavailable.' } }
});
const SafeWidget = boundary(Widget);withErrorBoundary(options, { Header, Sidebar }) wraps several components at once and returns them under the same keys.
On the server a boundary starts from a clean state on every call. Function components nested inside the wrapped component are evaluated within the boundary. See Error handling.
Performance Monitoring
performanceMonitor records metrics from renders made with enableMonitoring: true:
import { render, performanceMonitor } from '@coherent.js/core';
render(Page(), { enableMonitoring: true });
const report = performanceMonitor.generateReport();
console.log(report.metrics.renderTime.avg);Other methods: getStats(), reset(), measure(name, fn), measureAsync(name, fn), startRender() / endRender(id), addMetric(name, config), addAlertRule(rule), and start() / stop() for periodic resource sampling and reporting.
Database Layer
See the database guide for details.
import { createDatabaseManager, executeQuery } from '@coherent.js/database';
const db = createDatabaseManager({ type: 'sqlite', database: ':memory:' });
await db.connect();
await db.query('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, active INTEGER)');
const { rows } = await executeQuery(db, {
table: 'users',
select: ['id', 'name'],
where: { active: 1 },
orderBy: { name: 'ASC' },
limit: 10
});createQuery(config)returns a copy of the query object;executeQuery(db, config)validates it, builds the SQL and runs it.- Identifiers, operators,
orderBydirections andlimit/offsetare validated, and UPDATE/DELETE withoutwherethrows unlessallowFullTable: trueis passed. DatabaseManageris also available from@coherent.js/database/connection.
Client-side Hydration
hydrate(component, container, options?)
Hydrates server-rendered HTML with the same component so its on* handlers work.
Options:
initialState(Object): state to hydrate with; defaults to the container'sdata-stateattributeprops(Object): extra props passed to the componentdetectMismatch(Boolean): compare the DOM with the component's output. Defaults to on only whenprocess.env.NODE_ENV === 'development', or whenstrict/onMismatchis setstrict(Boolean): throw on mismatch instead of warningonMismatch(Function): receives the detected mismatches
Returns: { unmount, rerender, getState, setState }.
import { hydrate } from '@coherent.js/client';
import { Counter } from './components/Counter.js';
const container = document.getElementById('counter');
const instance = hydrate(Counter, container, {
initialState: { count: 10 },
props: { theme: 'dark' }
});
instance.setState({ count: 11 }); // patches the DOM
instance.unmount(); // releases handlers; later setState() does nothingHydrating a container again replaces the previous hydration.
extractState(element) / serializeState(state)
serializeState(state) returns the base64 string to put in a data-state attribute (or null when there is nothing serializable); extractState(element) reads it back.
import { extractState } from '@coherent.js/client';
const state = extractState(document.getElementById('counter')); // parsed data-state, or nullRemoved in 1.0
legacyHydrate, hydrateAll, hydrateBySelector, makeHydratable, autoHydrate, enableClientEvents and registerEventHandler were removed in 1.0 in favor of hydrate(). See MIGRATION-1.0.md.
Framework Integrations
All adapters live in @coherent.js/integrations/<framework> and render only what they are handed explicitly; plain objects stay JSON. See the integrations guide.
Express (@coherent.js/integrations/express)
setupCoherent(app, options?)installscoherentMiddleware, which addsres.coherent(component, { template? }). Options:template(with a{{content}}placeholder),enablePerformanceMonitoring,autoRender(render component-shaped objects passed tores.send, off by default),useEngine/engineName(register the view engine, off by default).createCoherentHandler(componentFactory, options?)— a route handler; the factory receives(req, res, next).
Fastify (@coherent.js/integrations/fastify)
setupCoherent/coherentFastifyis a Fastify plugin:await fastify.register(setupCoherent, { template }). It addsreply.coherent(component, { template? }), which returns the reply; render errors go through Fastify's error handling.autoRender: truealso renders component-shaped handler return values.createHandler(componentFactory, options?)— the factory receives(request, reply).
Koa (@coherent.js/integrations/koa)
setupCoherent(app, options?)installscoherentKoaMiddleware, which addsctx.coherent(component, { template? }).autoRender: truealso renders a component-shapedctx.body.createHandler(componentFactory, options?).
Next.js (@coherent.js/integrations/nextjs)
createCoherentAppRouterHandler(factory, options?)— App Router route handler; the factory receives(request, { params })(paramsis a Promise from Next.js 15 on).createCoherentNextHandler(factory, options?)— Pages Router API route handler.createCoherentServerComponent(factory, options?)/createCoherentClientComponent(factory, options?)— async; they resolve to React components. Pass{ React }to supply the React module explicitly.
Utilities
escapeHtml(text)— escapes&,<,>,"and'.validateComponent(component)— a quick structural check: throws when the value is not a component (e.g.nullor a number) and returnstrueotherwise.render()performs the full validation.isValidAttributeName(name)— whetherrender()accepts an attribute name.dangerouslySetInnerContent(html)/isTrustedContent(value)— mark raw HTML as trusted. Markers carry a non-enumerable symbol brand; plain objects such as{ __html, __trusted: true }parsed from JSON are never trusted.
Types
@coherent.js/core ships declarations for CoherentNode, CoherentElement, CoherentComponent, RenderOptions, StreamOptions and more:
import type { CoherentNode, RenderOptions } from '@coherent.js/core';
const Badge = ({ label }: { label: string }): CoherentNode => ({
span: { className: ['badge', label === 'new' && 'badge--new'], text: label }
});A CoherentNode is an element object, a string, a number, a boolean, null, undefined, or an array of nodes.