Documents the decision to use the Sentry SDK with self-hosted GlitchTip, sendDefaultPii:false rationale, errorId surfacing to users, and alternatives considered (Sentry SaaS rejected for data-minimisation reasons). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
3.9 KiB
ADR-018: GlitchTip frontend error tracking via @sentry/sveltekit
Date: 2026-05-17
Status: Accepted
Deciders: Marcel Raddatz
Context
The Familienarchiv had no client-side error reporting. When a user encountered a crash
or unhandled error in the SvelteKit frontend, there was no way for the operator to
observe it — errors were invisible until a user manually reported them. A GlitchTip
instance (self-hosted, Sentry-compatible) was already running as part of the
observability stack (docker-compose.observability.yml). The backend already reported
server-side errors to it.
We needed a way to:
- Capture frontend errors automatically and route them to GlitchTip.
- Give users a visible error identifier they can include in a support message.
- Do this without leaking personally identifiable information (PII) from the family archive — documents contain personal histories, names, and relationships.
Decision
Use @sentry/sveltekit (the official Sentry SDK for SvelteKit) to:
- Initialise with
sendDefaultPii: falseon bothhooks.server.tsandhooks.client.ts. - Pass a callback to
Sentry.handleErrorWithSentry()that returns{ message, errorId }whereerrorIdisSentry.lastEventId()when Sentry captured the event, or a freshcrypto.randomUUID()as fallback. - Display the
errorIdon the+error.sveltepage so users can include it in a report to the operator.
The SDK is initialised with enabled: !!import.meta.env.VITE_SENTRY_DSN so that
development and CI builds without a DSN configured do not send any events.
VITE_SENTRY_DSN is a write-only ingest key — it can POST events to GlitchTip but
cannot read them. It is safe to include in the client bundle per the Sentry security
model; it does not require rotation like a password.
Alternatives considered
Sentry SaaS — rejected. The archive contains private family documents and personal history. Sending error events with stack traces to a US-hosted third party is inconsistent with the project's data-minimisation posture. Self-hosted GlitchTip on the same Hetzner VPS keeps all data on infrastructure the operator controls.
Custom error logging endpoint — rejected. The @sentry/sveltekit SDK handles SvelteKit's hook lifecycle, source-map upload, and event grouping automatically. Reimplementing this would cost significant engineering time for no benefit.
Log-only (no user-visible errorId) — rejected. Without a visible error ID, users
can only describe what happened in natural language, making it hard to correlate a
report with a specific GlitchTip event. The errorId closes this gap at negligible UI
cost.
Consequences
Positive:
- Frontend errors are now observable without requiring user reports.
- Users can provide an
errorIdthat maps directly to a GlitchTip event. sendDefaultPii: falseensures names, IPs, and cookie values are not included in captured events.tracesSampleRate: 0.1limits trace volume to 10% of transactions, keeping GlitchTip load low on the shared VPS.
Negative / trade-offs:
- The
@sentry/sveltekitSDK is now a production dependency. SDK updates must be reviewed for changes to the default PII scrubbing behaviour. - The
handleErrorcallback in both hooks returns a hardcoded English message ('An unexpected error occurred'). This bypasses Paraglide i18n — the error page will always show English text when the hooks are active, regardless of the user's locale. This is acceptable because: (a) the error page is a last-resort fallback not part of normal UX, (b) theerrorIdis the actionable information, not the message text. A future ADR may address this if internationalised error messages become a requirement. Sentry.lastEventId()returnsundefinedwhen Sentry did not capture the event (e.g. DSN not configured). Thecrypto.randomUUID()fallback guarantees anerrorIdis always present, but that UUID will not appear in GlitchTip.