Skip to content

React 18 and React 19

Install @belvador/react for browser React apps (Vite, SPA), with separate wiring for React 18 and React 19.

Markdown
On this page

Use @belvador/react for React apps that render in the browser — Vite, Create React App or any other single-page setup. It includes everything in @belvador/browser, plus an error boundary and React 19 root hooks. Lovable and TanStack Start apps use TanStack Start instead.

Before you start#

  • A project with platform React (Vite / SPA) and its DSN.

  • React 18 or React 19. Check which one you have: npm ls react.

1. Install#

Terminal
npm install @belvador/react@0.6.1

2. Initialize#

Create src/instrument.ts. It starts Belvador:

src/instrument.ts
import { init } from "@belvador/react";init({  dsn: "https://<public-key>@ingest.belvador.dev/<project-id>",  release: import.meta.env.VITE_APP_VERSION, // your build or commit id  environment: import.meta.env.MODE,});

3. Import it first — React 18#

Make ./instrument the first import of src/main.tsx. Imports run before the rest of a file, so an init call written below import App never runs when one of App's modules fails while loading — a missing environment variable, for example. The page stays blank and nothing is reported. This version works with React 18 and 19. ErrorBoundary reports render errors (with the component stack) and shows the fallback instead of a blank page.

src/main.tsx
import "./instrument"; // first line: starts Belvador before App's modules loadimport { createRoot } from "react-dom/client";import { ErrorBoundary } from "@belvador/react";import App from "./App";const root = document.getElementById("root");if (!root) throw new Error("Root element missing");createRoot(root).render(  <ErrorBoundary fallback={<p>Something went wrong.</p>}>    <App />  </ErrorBoundary>,);

3. Import it first — React 19#

React 19 handles many errors itself and does not let them reach window.onerror. Pass reactErrorHandler() to the three root options so Belvador sees them. These options do not exist in React 18 — use the version above there.

src/main.tsx
import "./instrument"; // first line: starts Belvador before App's modules loadimport { createRoot } from "react-dom/client";import { ErrorBoundary, reactErrorHandler } from "@belvador/react";import App from "./App";const root = document.getElementById("root");if (!root) throw new Error("Root element missing");createRoot(root, {  onUncaughtError: reactErrorHandler(),  onCaughtError: reactErrorHandler(),  onRecoverableError: reactErrorHandler(),}).render(  <ErrorBoundary fallback={<p>Something went wrong.</p>}>    <App />  </ErrorBoundary>,);
Root optionReports
onUncaughtErrorErrors no error boundary caught
onCaughtErrorErrors an error boundary caught and rendered a fallback for
onRecoverableErrorErrors React recovered from on its own, for example hydration mismatches

reactErrorHandler takes an optional callback that runs after reporting: reactErrorHandler((error, errorInfo, eventId) => { … }).

4. Allow the ingest address in your CSP#

Text
connect-src 'self' https://ingest.belvador.dev;

Merge this into your existing connect-src; do not replace the policy.

Error boundaries for parts of the page#

Wrap risky parts of the UI so one failing widget does not take down the page:

TSX
import { ErrorBoundary, withErrorBoundary } from "@belvador/react";export function Dashboard() {  return (    <ErrorBoundary      fallback={({ resetError }) => (        <div role="alert">          <p>This chart failed to load.</p>          <button onClick={resetError}>Try again</button>        </div>      )}    >      <RevenueChart />    </ErrorBoundary>  );}// Or wrap a component once:export const SafeRevenueChart = withErrorBoundary(RevenueChart, {  fallback: <p>This chart failed to load.</p>,});

ErrorBoundary reports each render error once and shows fallback. The user-feedback dialog some SDKs offer is not available: it would load a script from the network.

Report errors you handle#

Everything from @belvador/browser is exported here too:

TSX
import { captureException, setUser } from "@belvador/react";setUser({ id: user.id });try {  await importCsv(file);} catch (error) {  captureException(error);  setStatus("failed");}

Check that it works#

Add a temporary button, click it, and open the project in Belvador:

TSX
function Crash(): never {  throw new Error("Belvador React test");}export function TestButton() {  const [crash, setCrash] = useState(false);  return crash ? <Crash /> : <button onClick={() => setCrash(true)}>Send test error</button>;}

The issue shows the component stack under Contexts. Remove the button afterwards.

Limits to know#

  • Event handlers (onClick, …) and async code are not covered by error boundaries — React's rule. Errors there that nobody catches still reach Belvador through the global handlers; errors you catch need captureException.

  • Server-side rendering frameworks other than TanStack Start are not covered by this package's setup.

Posts on this topic

All posts

Spotted something wrong or missing? Tell us: hello@belvador.dev