Errors and code splitting
Recover from component render failures, load component code on demand, and choose ErrorBoundary, lazy, or Deferred correctly.
Choose the boundary by what is pending
Use a page error export for a contextual route-loader failure.
Use
ErrorBoundaryfor a descendant component that throws while rendering.Use
lazywhen the component implementation should come from a dynamic import.Use
DeferredorcreateDeferredValuewhen a promise-backed data value should render fallback UI and participate in progressive SSR.Tavo.jsdoes not publish a Suspense component. Use the primitive that owns the actual failure or asynchronous work.
Recover from descendant render errors
ErrorBoundary renders its children until a descendant render throws. It then renders a static fallback or calls a fallback function with the error. Static and progressive server rendering apply the same fallback contract.
Changing
resetKeyunderObject.isclears a captured client error and retries the current children.Changing
resetKeydoes not fix the underlying state and does not reset an unrelated lazy-loader cache.If rendering the fallback also fails, the error continues to the parent boundary or runtime error reporting.
A boundary is not a replacement for route error exports, rejected action state, or expected form validation.
Fallback UI should be accessible, concise, and offer only recovery that can actually change the failing condition.
tsximport { ErrorBoundary } from "@tavojs/core";
function ProjectSummary({
project,
}: {
project: { name: string } | null;
}) {
if (!project) {
throw new Error("Project data is unavailable.");
}
return <h2>{project.name}</h2>;
}
export function ProjectPanel({
project,
version,
}: {
project: { name: string } | null;
version: number;
}) {
return (
<ErrorBoundary
resetKey={version}
fallback={(error: unknown) => {
const message =
error instanceof Error ? error.message : "Project failed to render.";
return <p role="alert">{message}</p>;
}}
>
<ProjectSummary project={project} />
</ErrorBoundary>
);
}Load component code on demand
lazy accepts a loader that resolves either a component or a module with a default component. Browser rendering starts one shared pending load, shows fallback UI, and rerenders mounted subscribers when the loader settles.
fallback receives idle or loading.
errorFallbackreceives error status and the caught loader error.If
errorFallbackis omitted, a failed load is thrown during the next render so the nearestErrorBoundarycan capture it.preload deduplicates the active load and resolves to the loaded component.
getStatusreports idle, loading, loaded, or error.Synchronous SSR does not start the loader; it renders fallback. Call preload before rendering when loaded server output is required.
A successful load is cached on that lazy component definition. A failed definition stays in error state; create a new definition or reload the owning module for a real retry.
tsximport { lazy } from "@tavojs/core";
import type { PageProps } from "@tavojs/core/router";
type ReportsData = {
points: number[];
};
const ReportsChart = lazy(
() => import("../components/ReportsChart"),
{
fallback: ({ status }) => {
return <p aria-busy="true">Chart {status}…</p>;
},
errorFallback: ({ error }) => {
const message =
error instanceof Error ? error.message : "Chart code failed to load.";
return <p role="alert">{message}</p>;
},
},
);
export async function load(): Promise<ReportsData> {
return { points: [12, 18, 25] };
}
export default function ReportsPage({
data,
}: PageProps<ReportsData>) {
return (
<main>
<h1>Reports</h1>
<ReportsChart points={data?.points ?? []} />
</main>
);
}Preload only when the server needs loaded output
Call preload from an explicit server preparation path before render, not from the component render function.
When fallback HTML is acceptable, let SSR render it and allow the browser to start loading after hydration.
Preloading changes code availability, not
Deferreddata state or route-loader caching.
tsxconst InvoicePreview = lazy(
() => import("../components/InvoicePreview"),
);
export async function prepareInvoicePreview() {
await InvoicePreview.preload();
}
export function InvoiceSection() {
return <InvoicePreview />;
}Verify every state and recovery path
Render a throwing child on the server and in the browser; assert the boundary fallback receives the error.
Change
resetKeywith corrected child inputs and confirm the subtree renders again.Hold a lazy loader pending and assert both idle/loading fallback behavior and final replacement.
Reject a lazy loader with and without
errorFallback; verify the local fallback or parentErrorBoundaryowns the error.Render lazy synchronously during SSR to confirm the loader is not called, then preload and confirm loaded output.
Look up exact public types
Follow linked API names to their canonical TypeScript declarations and package boundaries.