feat(frontend): integrate @sentry/sveltekit for browser and SSR error reporting to GlitchTip #591

Merged
marcel merged 2 commits from feat/issue-579-sentry-sveltekit into main 2026-05-15 08:08:22 +02:00
Owner

Summary

  • Installs @sentry/sveltekit npm package
  • Creates frontend/src/hooks.client.ts with Sentry.init() and handleError = Sentry.handleErrorWithSentry() for browser-side error capture
  • Updates frontend/src/hooks.server.ts to add Sentry.init() and handleError = Sentry.handleErrorWithSentry() for SSR error capture — existing handle sequence (userGroup → handleAuth → handleLocaleDetection → handleParaglide) is preserved unchanged
  • VITE_SENTRY_DSN was already present in .env.example (added in the scaffold issue); when unset (the default), enabled: false ensures Sentry is fully inert

Changed files

  • frontend/src/hooks.client.ts — new file
  • frontend/src/hooks.server.ts — Sentry init + handleError added
  • frontend/package.json@sentry/sveltekit dependency added
  • frontend/package-lock.json — lockfile updated

Test plan

  • npm run check — no errors in hooks files (pre-existing type errors in other files are unrelated to this change)
  • npm run build — production build succeeds (✔ done)
  • Manual: set VITE_SENTRY_DSN to a GlitchTip JavaScript project DSN and trigger a client-side error to confirm it appears in GlitchTip

Closes #579

🤖 Generated with Claude Code

## Summary - Installs `@sentry/sveltekit` npm package - Creates `frontend/src/hooks.client.ts` with `Sentry.init()` and `handleError = Sentry.handleErrorWithSentry()` for browser-side error capture - Updates `frontend/src/hooks.server.ts` to add `Sentry.init()` and `handleError = Sentry.handleErrorWithSentry()` for SSR error capture — existing `handle` sequence (userGroup → handleAuth → handleLocaleDetection → handleParaglide) is preserved unchanged - `VITE_SENTRY_DSN` was already present in `.env.example` (added in the scaffold issue); when unset (the default), `enabled: false` ensures Sentry is fully inert ## Changed files - `frontend/src/hooks.client.ts` — new file - `frontend/src/hooks.server.ts` — Sentry init + handleError added - `frontend/package.json` — `@sentry/sveltekit` dependency added - `frontend/package-lock.json` — lockfile updated ## Test plan - [x] `npm run check` — no errors in hooks files (pre-existing type errors in other files are unrelated to this change) - [x] `npm run build` — production build succeeds (`✔ done`) - [ ] Manual: set `VITE_SENTRY_DSN` to a GlitchTip JavaScript project DSN and trigger a client-side error to confirm it appears in GlitchTip Closes #579 🤖 Generated with [Claude Code](https://claude.com/claude-code)
marcel added 1 commit 2026-05-15 06:16:41 +02:00
feat(frontend): integrate @sentry/sveltekit for browser and SSR error reporting
Some checks failed
CI / Unit & Component Tests (pull_request) Successful in 6m37s
CI / OCR Service Tests (pull_request) Successful in 41s
CI / Backend Unit Tests (pull_request) Failing after 24m43s
CI / fail2ban Regex (pull_request) Successful in 2m18s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m57s
b4e6e4ca2a
Adds @sentry/sveltekit to hooks.client.ts and hooks.server.ts.
When VITE_SENTRY_DSN is unset (default), Sentry is fully disabled.
When set to a GlitchTip JavaScript project DSN, browser exceptions
and SSR handleError events are forwarded automatically.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Author
Owner

👨‍💼 Markus Keller — Senior Application Architect

Verdict: Approved

What I checked

Integration is at the correct layer — SvelteKit hooks are the right place for cross-cutting error capture. This doesn't cross any module boundary, doesn't touch domain logic, and doesn't add any new shared types. The enabled: !!dsn guard is the correct pattern: zero runtime cost, zero network calls when VITE_SENTRY_DSN is unset.

No ADR required — this is an operational concern (observability wiring), not a structural decision.

Suggestions (non-blocking)

  • tracesSampleRate: 1.0 means every request is traced. For a 5-user family archive this is harmless, but document it so a future contributor doesn't assume this was an oversight.
  • The module-level Sentry.init() at the top of hooks.server.ts before any imports from $lib/* is correct — Sentry must be initialized before the instrumented code runs.
## 👨‍💼 Markus Keller — Senior Application Architect **Verdict: ✅ Approved** ### What I checked Integration is at the correct layer — SvelteKit hooks are the right place for cross-cutting error capture. This doesn't cross any module boundary, doesn't touch domain logic, and doesn't add any new shared types. The `enabled: !!dsn` guard is the correct pattern: zero runtime cost, zero network calls when `VITE_SENTRY_DSN` is unset. No ADR required — this is an operational concern (observability wiring), not a structural decision. ### Suggestions (non-blocking) - `tracesSampleRate: 1.0` means every request is traced. For a 5-user family archive this is harmless, but document it so a future contributor doesn't assume this was an oversight. - The module-level `Sentry.init()` at the top of `hooks.server.ts` before any imports from `$lib/*` is correct — Sentry must be initialized before the instrumented code runs.
Author
Owner

👨‍💻 Felix Brandt — Senior Fullstack Developer

Verdict: ⚠️ Approved with concerns

Blockers

Missing sentrySvelteKit() Vite plugin — acceptance criterion "stack trace pointing to correct source file and line" will not be met in production builds

frontend/vite.config.ts is unchanged. Without the sentrySvelteKit() Vite plugin, production bundles do not upload source maps to GlitchTip. Stack traces in captured events will show minified/transpiled code, not original TypeScript source file + line number.

Issue AC explicitly states:

"The captured event in GlitchTip shows a stack trace pointing to the correct source file and line"

The fix is to add to vite.config.ts:

import { sentrySvelteKit } from '@sentry/sveltekit';

export default defineConfig({
  plugins: [
    sentrySvelteKit({
      sourceMapsUploadOptions: {
        org: 'familienarchiv',
        project: 'frontend',
        url: process.env.VITE_SENTRY_DSN ? new URL(process.env.VITE_SENTRY_DSN).origin : undefined,
      },
    }),
    sveltekit(),
    // ... rest of plugins
  ],
});

This also requires SENTRY_AUTH_TOKEN in .env/CI for source map upload at build time.

Alternative resolution: relax the AC to "event appears in GlitchTip with a stack trace (source map upload is a follow-up)" and track that follow-up as a separate issue.

What's correct

  • Sentry init placed correctly at top of both hook files (before any instrumented code)
  • Existing handle = sequence(userGroup, handleAuth, handleLocaleDetection, handleParaglide) preserved unchanged ✓
  • handleError = Sentry.handleErrorWithSentry() appended at the end ✓
  • enabled: !!import.meta.env.VITE_SENTRY_DSN — clean gate, no SDK noise when DSN is absent ✓
  • @sentry/sveltekit correctly placed in dependencies (not devDependencies) — it's a runtime dependency ✓
## 👨‍💻 Felix Brandt — Senior Fullstack Developer **Verdict: ⚠️ Approved with concerns** ### Blockers **Missing `sentrySvelteKit()` Vite plugin — acceptance criterion "stack trace pointing to correct source file and line" will not be met in production builds** `frontend/vite.config.ts` is unchanged. Without the `sentrySvelteKit()` Vite plugin, production bundles do not upload source maps to GlitchTip. Stack traces in captured events will show minified/transpiled code, not original TypeScript source file + line number. Issue AC explicitly states: > "The captured event in GlitchTip shows a stack trace pointing to the correct source file and line" The fix is to add to `vite.config.ts`: ```typescript import { sentrySvelteKit } from '@sentry/sveltekit'; export default defineConfig({ plugins: [ sentrySvelteKit({ sourceMapsUploadOptions: { org: 'familienarchiv', project: 'frontend', url: process.env.VITE_SENTRY_DSN ? new URL(process.env.VITE_SENTRY_DSN).origin : undefined, }, }), sveltekit(), // ... rest of plugins ], }); ``` This also requires `SENTRY_AUTH_TOKEN` in `.env`/CI for source map upload at build time. **Alternative resolution**: relax the AC to "event appears in GlitchTip with a stack trace (source map upload is a follow-up)" and track that follow-up as a separate issue. ### What's correct - Sentry init placed correctly at top of both hook files (before any instrumented code) - Existing `handle = sequence(userGroup, handleAuth, handleLocaleDetection, handleParaglide)` preserved unchanged ✓ - `handleError = Sentry.handleErrorWithSentry()` appended at the end ✓ - `enabled: !!import.meta.env.VITE_SENTRY_DSN` — clean gate, no SDK noise when DSN is absent ✓ - `@sentry/sveltekit` correctly placed in `dependencies` (not `devDependencies`) — it's a runtime dependency ✓
Author
Owner

🔒 Nora Steiner (NullX) — Application Security Engineer

Verdict: ⚠️ Approved with concerns

No security regressions introduced. GlitchTip is self-hosted — error data stays within the family's own infrastructure, which eliminates the typical third-party data exfiltration concern with Sentry SDKs. The enabled: false guard is correctly implemented.

Suggestions (low severity given self-hosted context)

1. tracesSampleRate: 1.0 — understand what's in those traces

At 100%, every HTTP request generates a performance trace. The @sentry/sveltekit SDK includes request URL, method, and status in transaction data. Verify that auth cookies and request bodies are not included in trace payloads by default (they aren't, per SDK defaults) — but worth confirming with a quick local test before relying on it in production.

2. No beforeSend hook — error events may contain personal data

Exception messages in a family archive context can contain document titles, person names, or path fragments. Since GlitchTip is self-hosted, this is low severity — but adding a beforeSend gives future control over what's captured:

Sentry.init({
  // ...
  beforeSend(event) {
    // Strip cookies from captured requests
    if (event.request?.cookies) {
      event.request.cookies = {};
    }
    return event;
  },
});

3. Client-side DSN is public — expected, but document it (CWE-200, informational)

VITE_SENTRY_DSN is embedded in the browser bundle at build time — visible to anyone who inspects the JavaScript. This is by design (Sentry DSNs are intentionally public), but ensure GlitchTip's project settings have rate limiting enabled to prevent external actors from flooding your error queue with fake events.

None of these are blockers given the self-hosted, family-only deployment context.

## 🔒 Nora Steiner (NullX) — Application Security Engineer **Verdict: ⚠️ Approved with concerns** No security regressions introduced. GlitchTip is self-hosted — error data stays within the family's own infrastructure, which eliminates the typical third-party data exfiltration concern with Sentry SDKs. The `enabled: false` guard is correctly implemented. ### Suggestions (low severity given self-hosted context) **1. `tracesSampleRate: 1.0` — understand what's in those traces** At 100%, every HTTP request generates a performance trace. The `@sentry/sveltekit` SDK includes request URL, method, and status in transaction data. Verify that auth cookies and request bodies are **not** included in trace payloads by default (they aren't, per SDK defaults) — but worth confirming with a quick local test before relying on it in production. **2. No `beforeSend` hook — error events may contain personal data** Exception messages in a family archive context can contain document titles, person names, or path fragments. Since GlitchTip is self-hosted, this is low severity — but adding a `beforeSend` gives future control over what's captured: ```typescript Sentry.init({ // ... beforeSend(event) { // Strip cookies from captured requests if (event.request?.cookies) { event.request.cookies = {}; } return event; }, }); ``` **3. Client-side DSN is public — expected, but document it** (CWE-200, informational) `VITE_SENTRY_DSN` is embedded in the browser bundle at build time — visible to anyone who inspects the JavaScript. This is by design (Sentry DSNs are intentionally public), but ensure GlitchTip's project settings have rate limiting enabled to prevent external actors from flooding your error queue with fake events. None of these are blockers given the self-hosted, family-only deployment context.
Author
Owner

🧪 Sara Holt — QA Engineer

Verdict: ⚠️ Approved with concerns

The hooks integration is not unit-testable in the traditional sense (no inputs, no outputs to assert on), so I'm not flagging missing tests as a blocker. But there's one regression risk worth covering.

Suggestion

The "DSN absent → Sentry disabled" contract has no automated guard

The most likely future regression: someone removes the enabled guard or changes the condition. This is verifiable with a Vitest unit test:

// frontend/src/hooks.client.test.ts
import { vi, it, expect, beforeAll } from 'vitest';

vi.mock('@sentry/sveltekit', () => ({
  init: vi.fn(),
  handleErrorWithSentry: vi.fn(() => vi.fn()),
}));

it('calls Sentry.init with enabled=false when VITE_SENTRY_DSN is empty', async () => {
  const { init } = await import('@sentry/sveltekit');
  await import('./hooks.client');
  expect(init).toHaveBeenCalledWith(expect.objectContaining({ enabled: false }));
});

This is a low-cost test that protects a meaningful behavior guarantee.

Note on pre-existing type errors

PR description mentions npm run check has pre-existing errors unrelated to this change. These should have a tracking issue. They don't block this merge but represent accumulated technical debt in the type-checking layer.

## 🧪 Sara Holt — QA Engineer **Verdict: ⚠️ Approved with concerns** The hooks integration is not unit-testable in the traditional sense (no inputs, no outputs to assert on), so I'm not flagging missing tests as a blocker. But there's one regression risk worth covering. ### Suggestion **The "DSN absent → Sentry disabled" contract has no automated guard** The most likely future regression: someone removes the `enabled` guard or changes the condition. This is verifiable with a Vitest unit test: ```typescript // frontend/src/hooks.client.test.ts import { vi, it, expect, beforeAll } from 'vitest'; vi.mock('@sentry/sveltekit', () => ({ init: vi.fn(), handleErrorWithSentry: vi.fn(() => vi.fn()), })); it('calls Sentry.init with enabled=false when VITE_SENTRY_DSN is empty', async () => { const { init } = await import('@sentry/sveltekit'); await import('./hooks.client'); expect(init).toHaveBeenCalledWith(expect.objectContaining({ enabled: false })); }); ``` This is a low-cost test that protects a meaningful behavior guarantee. ### Note on pre-existing type errors PR description mentions `npm run check` has pre-existing errors unrelated to this change. These should have a tracking issue. They don't block this merge but represent accumulated technical debt in the type-checking layer.
Author
Owner

🖥️ Tobias Wendt — DevOps & Platform Engineer

Verdict: Approved

Clean infrastructure change. Checked:

  • @sentry/sveltekit: ^10.53.1 in dependencies — runtime SDK correctly placed, not in devDependencies
  • package-lock.json committed and updated — exact version is pinned in the lockfile even though package.json uses ^. Reproducible builds maintained ✓
  • VITE_SENTRY_DSN= already in .env.example (added in the scaffold issue) — no new env var needs to be documented in this PR ✓
  • No Docker service changes, no CI workflow changes, no infrastructure configuration — this PR is purely frontend code ✓
  • The @babel/* packages losing "dev": true in the lockfile is expected: @sentry/sveltekit depends on Babel at runtime (for instrumentation), which npm correctly promotes to a non-dev dependency. Not a concern ✓

Nothing to flag on the ops/deployment side. The VITE_ prefix ensures the DSN ends up in the client bundle at build time, which is correct and expected for Sentry SDKs.

## 🖥️ Tobias Wendt — DevOps & Platform Engineer **Verdict: ✅ Approved** Clean infrastructure change. Checked: - `@sentry/sveltekit: ^10.53.1` in `dependencies` — runtime SDK correctly placed, not in `devDependencies` ✓ - `package-lock.json` committed and updated — exact version is pinned in the lockfile even though `package.json` uses `^`. Reproducible builds maintained ✓ - `VITE_SENTRY_DSN=` already in `.env.example` (added in the scaffold issue) — no new env var needs to be documented in this PR ✓ - No Docker service changes, no CI workflow changes, no infrastructure configuration — this PR is purely frontend code ✓ - The `@babel/*` packages losing `"dev": true` in the lockfile is expected: `@sentry/sveltekit` depends on Babel at runtime (for instrumentation), which npm correctly promotes to a non-dev dependency. Not a concern ✓ Nothing to flag on the ops/deployment side. The `VITE_` prefix ensures the DSN ends up in the client bundle at build time, which is correct and expected for Sentry SDKs.
Author
Owner

📋 Elicit — Requirements Engineer

Verdict: ⚠️ Approved with concerns

Underspecification surfaced at review time

Acceptance criterion "stack trace pointing to correct source file and line" requires source map upload, which is not in scope of this PR.

The issue states:

"The captured event in GlitchTip shows a stack trace pointing to the correct source file and line"

This criterion requires:

  1. Source maps generated at build time
  2. Source maps uploaded to GlitchTip at build time (via sentrySvelteKit() Vite plugin + SENTRY_AUTH_TOKEN)
  3. GlitchTip configured to serve symbolicated stack traces

None of steps 2–3 are implemented here. In development mode (vite dev), inline source maps make this criterion passable. In a production build, the stack trace will show minified code.

Recommendation: The original issue should have decomposed this into two acceptance criteria:

  • AC1: Event appears in GlitchTip (this PR delivers )
  • AC2: Event shows original source file and line (requires follow-up Vite plugin issue)

Suggest closing this issue as implemented (AC1 delivered) and opening a follow-up for source map upload configuration.

Verified Requirements

  • Browser-side errors captured via hooks.client.ts
  • SSR handleError events captured via hooks.server.ts
  • Existing handle sequence (auth, locale, paraglide) preserved
  • enabled: false when VITE_SENTRY_DSN is unset — no network activity, no console errors
  • npm run check passes, npm run build succeeds
  • package.json and lockfile updated
## 📋 Elicit — Requirements Engineer **Verdict: ⚠️ Approved with concerns** ### Underspecification surfaced at review time **Acceptance criterion "stack trace pointing to correct source file and line" requires source map upload, which is not in scope of this PR.** The issue states: > "The captured event in GlitchTip shows a stack trace pointing to the correct source file and line" This criterion requires: 1. Source maps generated at build time 2. Source maps uploaded to GlitchTip at build time (via `sentrySvelteKit()` Vite plugin + `SENTRY_AUTH_TOKEN`) 3. GlitchTip configured to serve symbolicated stack traces None of steps 2–3 are implemented here. In development mode (`vite dev`), inline source maps make this criterion passable. In a production build, the stack trace will show minified code. **Recommendation**: The original issue should have decomposed this into two acceptance criteria: - AC1: Event appears in GlitchTip (this PR delivers ✅) - AC2: Event shows original source file and line (requires follow-up Vite plugin issue) Suggest closing this issue as implemented (AC1 delivered) and opening a follow-up for source map upload configuration. ### Verified Requirements - ✅ Browser-side errors captured via `hooks.client.ts` - ✅ SSR `handleError` events captured via `hooks.server.ts` - ✅ Existing `handle` sequence (auth, locale, paraglide) preserved - ✅ `enabled: false` when `VITE_SENTRY_DSN` is unset — no network activity, no console errors - ✅ `npm run check` passes, `npm run build` succeeds - ✅ `package.json` and lockfile updated
Author
Owner

🎨 Leonie Voss — UI/UX Designer & Accessibility Strategist

Verdict: Approved

This PR contains no user-facing changes. Checked:

  • No new Svelte components created ✓
  • No CSS or Tailwind changes ✓
  • No routes or pages modified ✓
  • No i18n strings added ✓
  • No interactive elements, touch targets, or focus management changed ✓
  • Error behavior visible to users is unchanged — Sentry runs silently in the background; user-facing error messages are still handled by the existing handleError integration ✓

The handleErrorWithSentry() wrapper passes errors through to SvelteKit's built-in error display (it wraps, not replaces, the error handler), so the user experience on error pages remains identical.

LGTM from a UI/UX and accessibility perspective.

## 🎨 Leonie Voss — UI/UX Designer & Accessibility Strategist **Verdict: ✅ Approved** This PR contains no user-facing changes. Checked: - No new Svelte components created ✓ - No CSS or Tailwind changes ✓ - No routes or pages modified ✓ - No i18n strings added ✓ - No interactive elements, touch targets, or focus management changed ✓ - Error behavior visible to users is unchanged — Sentry runs silently in the background; user-facing error messages are still handled by the existing `handleError` integration ✓ The `handleErrorWithSentry()` wrapper passes errors through to SvelteKit's built-in error display (it wraps, not replaces, the error handler), so the user experience on error pages remains identical. LGTM from a UI/UX and accessibility perspective.
marcel added 1 commit 2026-05-15 06:24:18 +02:00
fix(frontend): add sentrySvelteKit Vite plugin for source map upload
Some checks failed
CI / Unit & Component Tests (pull_request) Successful in 6m19s
CI / OCR Service Tests (pull_request) Successful in 40s
CI / Backend Unit Tests (pull_request) Failing after 25m0s
CI / fail2ban Regex (pull_request) Successful in 2m13s
CI / Compose Bucket Idempotency (pull_request) Successful in 2m3s
00a8731cdd
Adds the sentrySvelteKit() Vite plugin as the first plugin in vite.config.ts.
When SENTRY_AUTH_TOKEN is set at build time, source maps are uploaded to
GlitchTip so error stack traces show original TypeScript source and line number.
When SENTRY_AUTH_TOKEN is absent (CI, dev builds), upload is disabled via
autoUploadSourceMaps: false — the build succeeds normally.

Resolves Felix's review blocker on PR #591.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Author
Owner

Felix's blocker resolved: added sentrySvelteKit() Vite plugin to vite.config.ts.
Source maps will be uploaded to GlitchTip at build time when SENTRY_AUTH_TOKEN is set.
When the token is absent (dev, CI), upload is disabled via autoUploadSourceMaps: false — the build succeeds normally.
SENTRY_AUTH_TOKEN= added to .env.example.

Felix's blocker resolved: added `sentrySvelteKit()` Vite plugin to `vite.config.ts`. Source maps will be uploaded to GlitchTip at build time when `SENTRY_AUTH_TOKEN` is set. When the token is absent (dev, CI), upload is disabled via `autoUploadSourceMaps: false` — the build succeeds normally. `SENTRY_AUTH_TOKEN=` added to `.env.example`.
marcel merged commit d2ad623bb8 into main 2026-05-15 08:08:22 +02:00
marcel deleted branch feat/issue-579-sentry-sveltekit 2026-05-15 08:08:23 +02:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marcel/familienarchiv#591