Error tracking
Error tracking
Both apps/api and apps/ui ship Sentry SDKs configured for zero
overhead. Swap between Hosted Sentry and self-hosted GlitchTip with a single
DSN key.
Sentry API
Protocol
Self-host
Opt-in
1
unified SDK
Both apps/api and apps/ui ship a Sentry SDK. The same SDK talks to:
- Hosted Sentry; fastest setup, paid past the free tier.
- Self-hosted GlitchTip; Sentry-API-compatible, runs as an overlay in the infra stack (
WITH_GLITCHTIP=1). Zero per-event cost.
The SDKs don’t know which one they’re talking to. Choosing is a one-env-var change.
Design choices
Section titled “Design choices”Sentry SDK on both sides (not a custom shim)
Same SDK interface and error format across both services.
GlitchTip as the self-host option
Wire-compatible with Sentry; runs on the same Postgres + Valkey the app already uses.
Empty DSN: SDK is a no-op
Dev stays clean; tests do not ship error reports.
Replay-on-error on, full-session replays off
Captures the broken flow without storing healthy sessions.
Single env var per side: SENTRY_DSN (API), VITE_SENTRY_DSN (UI)
Same protocol regardless of backend; swap is one redeploy.
How it’s wired
Section titled “How it’s wired”flowchart LR
api["apps/api<br/>@sentry/bun"] -- "events" --> backend{DSN points where?}
ui["apps/ui<br/>@sentry/react"] -- "events + replays-on-error" --> backend
backend -- "https://...sentry.io/..." --> sentry["hosted Sentry"]
backend -- "https://...glitchtip.localhost/..." --> glitchtip["self-hosted GlitchTip"]
Both apps/api (@sentry/bun) and apps/ui (@sentry/react) emit events to
the same Sentry-compatible wire protocol. The DSN env var picks the
destination: a sentry.io hostname for hosted Sentry, or a
glitchtip.localhost hostname for the self-hosted overlay. The
SDKs don’t know which one they’re talking to.
API side: Sentry initialises once at boot. If SENTRY_DSN is empty, init is a no-op. The shared captureError helper is wired into unhandled-rejection and uncaught-exception handlers, so anything that escapes the request loop reaches the backend.
UI side: Sentry initialises once at app mount when VITE_SENTRY_DSN is set. Replays-on-error capture the error context; full-session replays are off to avoid capturing video of every session.
Self-hosting with GlitchTip
Section titled “Self-hosting with GlitchTip”GlitchTip is Apache-licensed and Sentry-API-compatible. The infra stack provides it as an overlay:
WITH_GLITCHTIP=1 ./dev.sh up -dFirst boot bootstraps a superuser, a default org, and two projects (API and Frontend). Visit http://glitchtip.localhost, grab each project’s DSN, and drop them into the matching env vars.
The overlay reuses the base stack’s Postgres (in a separate glitchtip database) and Valkey (DB 1). Adding GlitchTip costs two extra containers, not a separate database server.
For production hardening (Basic Auth, HTTPS, real SMTP), see the GlitchTip docs under infra/compose.
Switching backends
Section titled “Switching backends”Change only the DSN:
Sentry (hosted)
API: SENTRY_DSN=https://...@sentry.io/.... UI:
VITE_SENTRY_DSN=https://...@sentry.io/....
GlitchTip (self-hosted)
API: SENTRY_DSN=https://...@glitchtip.example.com/.... UI:
VITE_SENTRY_DSN=https://...@glitchtip.example.com/....
No SDK changes. The infrastructure is the variable, not the code.
Source
Section titled “Source”- API init:
src/config/sentry.ts+src/config/error-handlers.ts. - UI init:
src/app/main.tsx. - GlitchTip overlay:
compose/docker-compose.glitchtip.yml.
Related
Section titled “Related”- Observability; metrics and logs that sit alongside error events.
- Profiles & overlays; how
WITH_GLITCHTIP=1actually composes onto the stack. - Security pipeline; the broader telemetry surface this fits into.
- Notifications; how user-facing errors are surfaced back into the app.