Error handling
Use this page when you need to answer "what does the user actually see when this fails?" Kovo has different paths for a query-backed region, a typed mutation failure, a route outcome, and an unexpected request-shell exception.
Wrap a region#
Start with the smallest region-level fallback:
// Source-verified shape from packages/core/src/index.ts
import { ErrorBoundary, component } from '@kovojs/core';
declare function ProductGrid(): string;
declare function ProductGridError(): string;
export const CatalogPage = component({
render: () => (
<ErrorBoundary fallback={<ProductGridError />}>
<ProductGrid />
</ErrorBoundary>
),
});This is the right tool when one part of the page can fail without taking down the whole document.
Run it#
Make the loader or render path throw inside that region. The boundary fallback renders in the same spot, and the rest of the document keeps its ordinary shell.
Use route-level outcomes when the whole page should change shape instead:
import { notFound, route } from '@kovojs/server';
export const accountRoute = route('/account/:id', {
async page({ params }) {
if (params.id === 'missing') return notFound();
return <main>Account</main>;
},
});Add the production shape#
Mutation failures are their own path. Expected form errors stay typed and local to the submitted form; they are not exceptions:
// Source-verified shape from packages/core/src/index.ts
import { FieldError, FormError, component } from '@kovojs/core';
declare const saveProfile: unknown;
export const ProfileForm = component({
render: () => (
<form mutation={saveProfile}>
<input name="displayName" />
<FieldError name="displayName" />
<FormError code="DUPLICATE_NAME">That name is taken.</FormError>
</form>
),
});Unexpected enhanced-mutation failures use the browser runtime's response posture:
- 401 with
Kovo-Reauthredirects through the re-auth path. - 403 stays an authorization failure.
- 500 becomes a render error, not a fake typed form failure.
For document-level shells, configure them once on the app:
import { defineKovo } from '@kovojs/server';
const ErrorShell = () => 'Server error';
const ForbiddenShell = () => 'Forbidden';
const NotFoundShell = () => 'Not found';
const app = defineKovo({
errorShells: { forbidden: ForbiddenShell, notFound: NotFoundShell, serverError: ErrorShell },
});
export default app.assemble({});Handle failure#
Choose the narrowest failure surface that matches the app:
- Use
<ErrorBoundary>for one query-backed or render-heavy region. - Use
context.fail(...),<FieldError>, and<FormError>for expected form failures. - Use
notFound()or a guard outcome when the whole route should change shell. - Use app
errorShellsandonErrorfor unexpected request-shell failures.
Observe it in production#
The request shell gives you one hook for unexpected exceptions:
import { defineKovo } from '@kovojs/server';
const app = defineKovo({
onError(error, context) {
console.error('request failed', context.operation, error);
},
});
export default app.assemble({});Keep that hook for logging and reporting. It is not the place to invent alternate response bodies.
Next#
- Mutations & forms — build the typed 422 path in detail.
- Request shell — wire document-level error shells and request hooks.
Spec & diagnostics
ErrorBoundary, FieldError, and FormError: packages/core/src/index.ts and
packages/server/src/jsx-runtime.ts. Route outcomes such as notFound(): site/content/guides/routing.md
and the server routing surface. App error shells and onError: packages/server/src/app-types.ts,
packages/server/src/app.ts, and site/content/guides/request-shell.md. Enhanced mutation reauth
handling lives in packages/browser/src/mutation-fetch.ts and the wire contract in spec/09-wire-protocol.md.
API reference: @kovojs/core, @kovojs/server.