Error handling
Errors throw or reject bare. There is no component-level boundary that catches
them mid-tree: once HTML is sent, nothing can replace it, so a server error is a
500 or a fallback page. For streaming, @vincle/flow recovers per fragment with
onError.
Top-level errors#
Wrap renderToString in try/catch for a global fallback:
import { renderToString } from "@vincle/core";
try { const html = await renderToString(<Page />); // send html to client} catch (error) { console.error("Page render failed:", error); // send a 500 or fallback page}The try catches errors both while <Page /> is constructed and while it
renders; a .catch() on the returned promise misses the first kind.
An Error thrown by a component is prefixed with the name of the innermost
component that threw: [Profile] not found. Any other thrown value reaches
catch exactly as thrown.
Message format#
Every error vincle raises says where it came from, what went wrong, why, and how to fix it:
[vincle/<package>] <api or component>: <what happened>. <why>. <how to fix it>[vincle/flow] <Defer target="hero">: merge="prepend" is not supported by this adapter: it supports: replace, append. Pick one of those, or use an adapter that supports "prepend".The prefix ([vincle/core], [vincle/flow], [vincle/vite-plugin],
[vincle/precompile]) is stable, for filtering logs. In code, read
error.code instead of the message.
Error codes#
Every error the libraries throw at render or setup carries a stable code, following Node's convention.
The type alone cannot tell a vincle refusal from a bug in your own component:
try { html = await renderToString(<Page />);} catch (error) { if (error.code === "ERR_VINCLE_VOID_CHILDREN") { // the markup is wrong, not the data }}The codes are not exported: compare against the string, as you would an
ENOENT. The set may grow; an existing code will not be renamed or reused.
@vincle/core#
| Code | Raised when |
|---|---|
ERR_VINCLE_INVALID_TAG |
a tag name that cannot be written as HTML |
ERR_VINCLE_VOID_CHILDREN |
content inside a void element (<br>, <img>, …) |
ERR_VINCLE_DANGEROUS_HTML |
dangerouslySetInnerHTML.__html is not a string |
ERR_VINCLE_FUNCTION_ATTR |
a function passed as an attribute value |
ERR_VINCLE_VNODE_AS_TEXT |
a component interpolated where text belongs |
ERR_VINCLE_SCOPE_COLLISION |
overlapping renders or Scope.with() with no AsyncLocalStorage |
ERR_VINCLE_NO_STORE |
the Scope store was read before Scope.with() ran |
ERR_VINCLE_NO_SCOPE |
a Scope call outside any Scope.with() |
ERR_VINCLE_CONTEXT_KEY |
Scope.key() given something other than a non-empty string |
ERR_VINCLE_CONTEXT_LIMIT |
more distinct context keys than the table holds |
ERR_VINCLE_CONTEXT_UNSET |
Scope.get() read a value never set in scope |
ERR_VINCLE_CONTEXT_CHILDREN |
a context Consumer whose children is not a function |
The first three are TypeErrors; every other code on this page is a plain
Error.
@vincle/flow#
| Code | Raised when |
|---|---|
ERR_VINCLE_FLOW_CONFIG |
an option rejected at the entry point that took it |
ERR_VINCLE_FLOW_NO_ADAPTER |
a placeholder or fragment with no adapter configured |
ERR_VINCLE_FLOW_MERGE_UNSUPPORTED |
a merge the adapter cannot express |
ERR_VINCLE_FLOW_FRAGMENT_ID |
a fragment id that cannot be a DOM id or URL segment |
ERR_VINCLE_FLOW_DUP_FRAGMENT |
two <Defer> on the same target in one render |
ERR_VINCLE_FLOW_NO_FRAGMENT |
renderFragment produced nothing for that id |
ERR_VINCLE_FLOW_NO_STREAMING |
renderToStream given an adapter that cannot stream |
@vincle/vite-plugin and @vincle/precompile#
| Code | Raised when |
|---|---|
ERR_VINCLE_VITE_MANIFEST_READ |
the Vite manifest could not be read |
ERR_VINCLE_VITE_MANIFEST_PARSE |
the manifest is not JSON |
ERR_VINCLE_VITE_MANIFEST_SHAPE |
the file parsed but is not a manifest |
ERR_VINCLE_VITE_CONFIG |
setVite given an option it cannot use |
ERR_VINCLE_VITE_MISSING_ENTRY |
an entry the manifest does not list |
ERR_VINCLE_PRECOMPILE_CONFIG |
runtimeSource is not a module specifier |
ERR_VINCLE_PRECOMPILE_HELPER |
a runtime helper answered a shape the transform cannot use |
ERR_VINCLE_PRECOMPILE_INTERNAL |
an invariant of the transform broke: a bug to report |
Configuration errors fail fast, at the entry point that receives the
option, before anything renders or a build starts. One exception is not an
error: an unreadable runtime module makes @vincle/precompile warn and fall
back to Deno's output.
Per-fragment errors (streaming)#
@vincle/flow takes onError on the render options and on each <Defer>:
serve(req, () => <Page />, NativeAdapter, { onError: (error, { id, kind }) => { console.error(`Fragment ${id} failed:`, error); return <p>This section failed to load.</p>; },});See renderToStream for details.