Skip to content

React SDK

@goliapkg/sentori-react is the React adapter on top of @goliapkg/sentori-javascript. It exposes:

  • <SentoriProvider> — drop near the root, initialises the JS SDK
  • <SentoriErrorBoundary> — class boundary wired to captureException
  • useSentori() / useCaptureError() — hooks for imperative capture
Terminal window
bun add @goliapkg/sentori-react
# or
npm install @goliapkg/sentori-react

Peer dependency: react >= 18.

import { SentoriProvider } from '@goliapkg/sentori-react'
export function Root() {
return (
<SentoriProvider
config={{
token: import.meta.env.VITE_SENTORI_TOKEN,
release: `myapp@${import.meta.env.VITE_RELEASE}`,
environment: import.meta.env.MODE,
ingestUrl: 'https://ingest.sentori.golia.jp',
}}
>
<App />
</SentoriProvider>
)
}

The provider is idempotent under React StrictMode double-mount.

Class component that catches render-phase errors in its subtree, forwards them to captureException, and renders a fallback.

PropTypeRequiredNotes
fallbackReactNode | (props: { error, reset }) => ReactNodeStatic node or render-prop. Use the render-prop form when you need access to error / reset.
onError(error, info) => voidRuns after Sentori capture. Use for app-side logging, never for capture (the boundary already captures).
resetKeysunknown[]Shallow-compared on update. Any change clears the caught error so children re-render. Use a route path, a query key, or a user id.
childrenReactNodeThe subtree to guard.

Wrap each route so a thrown error scoped to that route doesn’t kill the whole app shell.

import { Route, Routes, useLocation } from 'react-router'
import { SentoriErrorBoundary } from '@goliapkg/sentori-react'
function RouteBoundary({ children }: { children: React.ReactNode }) {
const location = useLocation()
return (
<SentoriErrorBoundary
fallback={<RouteErrorScreen />}
resetKeys={[location.pathname]}
>
{children}
</SentoriErrorBoundary>
)
}
export function AppRoutes() {
return (
<Routes>
<Route path="/" element={<RouteBoundary><Home /></RouteBoundary>} />
<Route path="/orders" element={<RouteBoundary><Orders /></RouteBoundary>} />
</Routes>
)
}

resetKeys={[location.pathname]} makes navigating away from the broken page clear the boundary automatically — the user doesn’t have to click a retry button.

Use the render-prop form to surface reset to the user.

<SentoriErrorBoundary
fallback={({ error, reset }) => (
<div className="error-card">
<h2>Something went wrong.</h2>
<p>{error.message}</p>
<button onClick={reset} type="button">
Try again
</button>
</div>
)}
>
<DataTable />
</SentoriErrorBoundary>

For a flaky data fetch, pair this with resetKeys={[queryKey]} so a fresh query also recovers the boundary even without the click.

Recipe 3 — “Report this” feedback button

Section titled “Recipe 3 — “Report this” feedback button”

onError runs after Sentori has already captured the event, so you can grab a correlation id and offer a feedback hook.

import { useState } from 'react'
function ReportableBoundary({ children }: { children: React.ReactNode }) {
const [eventId, setEventId] = useState<string | null>(null)
return (
<SentoriErrorBoundary
fallback={({ error, reset }) => (
<div className="error-card">
<h2>{error.message}</h2>
{eventId && <small>Report id: {eventId}</small>}
<button
onClick={() => openFeedbackModal(eventId)}
type="button"
>
Report this
</button>
<button onClick={reset} type="button">Dismiss</button>
</div>
)}
onError={(_err, _info) => {
// crypto.randomUUID is supported in every browser Sentori targets.
setEventId(crypto.randomUUID())
}}
>
{children}
</SentoriErrorBoundary>
)
}

The “report id” is a client-side correlation id you generate; pair it with a server-side feedback endpoint or paste it directly into a support ticket so engineering can correlate against the captured event.

import { useCaptureError, useSentori } from '@goliapkg/sentori-react'
function PayButton() {
const capture = useCaptureError()
const { addBreadcrumb } = useSentori()
return (
<button
onClick={async () => {
addBreadcrumb('user', { action: 'pay.click' })
try {
await pay()
} catch (err) {
capture(err as Error, { tags: { feature: 'checkout' } })
}
}}
type="button"
>
Pay
</button>
)
}

useSentori() returns the full context value (capture, breadcrumb, setUser, setTags). useCaptureError() is a shortcut for the common case.

One-liner that composes <Suspense> with <SentoriErrorBoundary>. Use when a data-fetching subtree should share the same fallback for both the loading state and the caught-error state, or pass errorFallback to differ them.

import { SentoriSuspense } from '@goliapkg/sentori-react'
<SentoriSuspense
errorFallback={<ErrorCard />}
fallback={<Skeleton />}
>
<UserProfile />
</SentoriSuspense>

Anything that throws during render of <UserProfile> — whether it’s a synchronous throw or a Suspense-surfaced rejected promise from use(promise) — is caught by the inner boundary and forwarded to captureException with tags.source = 'react.errorBoundary'.

Equivalent to writing the two-component form by hand:

<SentoriErrorBoundary fallback={<ErrorCard />}>
<Suspense fallback={<Skeleton />}>
<UserProfile />
</Suspense>
</SentoriErrorBoundary>

useSentoriRouter() subscribes to the react-router location and emits a nav breadcrumb on every transition. Imported from the /router subpath so apps that don’t use react-router don’t pay the peer-dependency cost:

import { BrowserRouter, Outlet, Route, Routes } from 'react-router'
import { SentoriProvider } from '@goliapkg/sentori-react'
import { useSentoriRouter } from '@goliapkg/sentori-react/router'
function Shell() {
useSentoriRouter()
return <Outlet />
}
export function App() {
return (
<SentoriProvider config={config}>
<BrowserRouter>
<Routes>
<Route element={<Shell />}>
<Route element={<Home />} path="/" />
<Route element={<Orders />} path="/orders" />
</Route>
</Routes>
</BrowserRouter>
</SentoriProvider>
)
}

Breadcrumb shape:

{
"type": "nav",
"data": { "from": "/", "to": "/orders?status=open" },
"timestamp": "2026-05-11T13:24:09.421Z"
}

Notes:

  • Peer dependency: react-router >= 7. Earlier versions split the package into react-router-dom; if you’re still on v6, alias the import or upgrade — Sentori does not maintain a v6 shim.
  • The hook is optional in peerDependenciesMeta, so npm/bun install will not warn if react-router isn’t in your tree.
  • First mount does not emit a breadcrumb — only real transitions (pathname / search / hash change) do. This avoids polluting the ring buffer with the initial route on every page load.
  • Mount once per Router, high in the tree (typically in a layout route’s component). Mounting in every page works but adds noise.

@goliapkg/sentori-next/app-router ships two App Router-specific hooks that build on the React SDK:

// app/Shell.tsx — client component, mounted from app/layout.tsx
'use client'
import { useNextRouter } from '@goliapkg/sentori-next/app-router'
export function Shell({ children }: { children: React.ReactNode }) {
useNextRouter() // nav breadcrumb on every pathname change
return <>{children}</>
}
app/error.tsx
'use client'
import { useReportNextError } from '@goliapkg/sentori-next/app-router'
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string }
reset: () => void
}) {
useReportNextError(error) // captureException once per error instance
return (
<div>
<h2>Something went wrong.</h2>
<button onClick={reset} type="button">Try again</button>
</div>
)
}

Three error surfaces in Next.js App Router and where Sentori catches them:

SurfaceWhere it’s caught
Server Components (RSC)instrumentation.ts:onRequestError (server bundle)
Route handlers (route.ts)instrumentation.ts:onRequestError (server bundle)
Client componentsapp/error.tsxuseReportNextError(error)

The instrumentation.ts side is a one-line re-export — see the sentori-next README for the full setup.

It is not a profiler and not a tracing SDK. Those land in v0.4.