feat(frontend): integrate @sentry/sveltekit for browser and SSR error reporting to GlitchTip #579
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
GlitchTip speaks the Sentry SDK wire protocol. Adding
@sentry/sveltekitto the frontend captures:handleErrorevents from both the client and server (SSR) runtimeWhen
VITE_SENTRY_DSNis empty (default in.env.example), Sentry is fully disabled — no network requests, no console output. This means CI and environments without GlitchTip running are unaffected.Depends on: GlitchTip infrastructure issue — a project DSN must exist before this can be tested end-to-end
Installation
Files to Create / Modify
frontend/src/hooks.client.ts(create or merge with existing)frontend/src/hooks.server.ts(create or merge with existing)If
hooks.server.tsalready exports ahandlefunction, wrap it withsequence():.env.example— already added by scaffold issue; confirm this line exists:frontend/.env(local only, not committed) — set after GlitchTip project is created:Acceptance Criteria
npm run checkpasses with no new type errorsnpm run buildsucceedsnpm run testpasses — no test regressionsVITE_SENTRY_DSNis unset or empty, the app starts normally with no Sentry-related console errors or network requestsVITE_SENTRY_DSNis set to the GlitchTip JavaScript project DSN: throwingthrow new Error("test")in a page'sloadfunction causes an event to appear in GlitchTip Issues within 30 secondsDefinition of Done
package.jsonandpackage-lock.json(orpnpm-lock.yaml) updatedhooks.client.tsandhooks.server.tscommittedmain🏗️ Markus Keller — Senior Application Architect
Observations
docs/architecture/c4/l1-context.puml(new external system: GlitchTip) anddocs/architecture/c4/l2-containers.puml(SvelteKit now emits to GlitchTip). I checked both files: neither currently mentions GlitchTip or error tracking. These updates are a required doc update, not optional.sequence(existingHandle)if ahandleexport exists.hooks.server.tsalready exportshandle = sequence(userGroup, handleAuth, handleLocaleDetection, handleParaglide). The Sentryhandlemiddleware must be threaded into this existing sequence correctly — typically prepended so errors in all downstream handlers are caught.tracesSampleRate: 1.0captures 100% of transactions. For a family project on a budget VPS, this is likely fine at low volume, but it will produce noise in GlitchTip's performance view and generates more outbound traffic. Setting it to0(errors only, no performance tracing) would be more appropriate for a first integration focused purely on error reporting.l1-context.pumlandl2-containers.pumlupdates are real required docs.Recommendations
tracesSampleRate: 0for the initial integration, not1.0. The project is using GlitchTip for error reporting, not distributed tracing.1.0is over-engineered for this use case.sequence(sentryHandle, userGroup, handleAuth, handleLocaleDetection, handleParaglide). This ensures auth errors and locale errors are also captured.l1-context.pumlandl2-containers.pumlas part of this PR. GlitchTip is a new external system the SvelteKit container depends on.👨💻 Felix Brandt — Senior Fullstack Developer
Observations
hooks.client.tsdoes not yet exist — it needs to be created, which is correctly called out in the issue.hooks.server.tsalready exists and exports ahandlechain viasequence(userGroup, handleAuth, handleLocaleDetection, handleParaglide). The issue mentions the merge pattern but the code snippet shows onlysequence(existingHandle)(singular). The actual call needs to sequence Sentry's handle alongside all four existing handles:sequence(Sentry.sentryHandle(), userGroup, handleAuth, handleLocaleDetection, handleParaglide).hooks.client.tsandhooks.server.tshave identicalSentry.init()blocks. The DSN, environment, and options are identical. This is intentional (client and server each need their own init call for the respective SDK runtime), but it's worth noting that a shared config object cannot be extracted here — the two SDKs are different bundles (browser vs Node). No refactor needed.enabled: !!import.meta.env.VITE_SENTRY_DSNguard is correct. An empty string is falsy, so an unset env var truly disables the SDK.hooks.ts(the reroute-only file) does not need to change — confirmed by inspection.package.jsonandpackage-lock.json(orpnpm-lock.yaml) — this project uses npm, so onlypackage-lock.jsonapplies. Thepnpm-lock.yamlalternative is irrelevant here.npm run generate:api) is needed — this is a pure hooks integration with no new API endpoints.Recommendations
hooks.server.ts, useSentry.sentryHandle()(if the package exposes it) as the first entry in the sequence. Check the@sentry/sveltekitAPI surface before implementing — the exact export name matters. If the package only exposeshandleErrorand not ahandlemiddleware, the issue's warning about wrapping is moot.VITE_SENTRY_DSNunset and assert no Sentry network call is attempted. This is the most important AC to automate.Sentry.init()calls strictly identical in options — any divergence between client and server init is a future confusion trap.🔒 Nora "NullX" Steiner — Application Security Engineer
Observations
Data exposure via error events
Sentry/GlitchTip events capture the full error context, which in a SvelteKit app can include request objects, locals, and cookies.
event.locals.useris populated byhooks.server.tsand includes the fullAppUserobject (groups, permissions, email). If SvelteKit passes the full event context tohandleError, this user object could end up in an error report.The
@sentry/sveltekithandleErrorWithSentry()wrapper does not scrub PII by default. For a family archive with real names and personal documents, this matters even though GlitchTip is self-hosted.DSN security
VITE_SENTRY_DSNis a Vite public env var (prefixedVITE_). This means it is embedded in the client-side bundle and visible to anyone who inspects the JavaScript. This is expected and correct — GlitchTip DSNs are designed to be public (they are write-only, with no read access). However, the implementer should confirm GlitchTip's DSN is truly write-only with no dangerous cross-site capabilities before shipping.Source maps
The issue spec does not mention source maps. Meaningful stack traces (the last AC: "stack trace pointing to the correct source file and line") require uploading source maps to GlitchTip. Source maps contain your full source code. Uploading them to GlitchTip (self-hosted) is fine for privacy. The
@sentry/sveltekitVite plugin can auto-upload them — but this requires a Sentry auth token. If source maps are not uploaded, the AC about correct file/line cannot be verified.tracesSampleRate: 1.0
Performance tracing at 100% is not a security concern per se, but it does mean every request generates an outbound network call to GlitchTip. If GlitchTip is unavailable, this could slow SSR response times. Use
0unless performance tracing is an explicit goal.Recommendations
beforeSendhook to scrubevent.useror any PII from error events before they reach GlitchTip:tracesSampleRate: 0(errors only) until a deliberate decision is made to enable performance tracing.🧪 Sara Holt — QA Engineer & Test Strategist
Observations
Test coverage for acceptance criteria
The issue has six ACs, of which only one ("when DSN is unset, no Sentry errors or network requests") is practically automatable as a unit/integration test. The remaining ACs are manual or E2E-only:
page.on('console', ...)captureThe issue lists no automated tests in the acceptance criteria. This is a gap for a feature that touches both the SSR and client runtimes.
CI impact
The
@sentry/sveltekitpackage adds a Vite plugin that may interact with the existingvite.config.ts. The CI workflow runsnpm run test:coverageandnpm run check. Both should still pass, but the new package is a build dependency that must not cause type errors or import resolution failures in the Vitest environment (whereimport.meta.env.VITE_SENTRY_DSNmay be undefined).The "DSN empty" path in test environment
Vitest's
import.meta.envdoes not automatically provideVITE_SENTRY_DSN. If the Sentry init code runs during tests and finds an undefined DSN, it should silently skip init. Theenabled: !!import.meta.env.VITE_SENTRY_DSNguard handles this — but it should be verified in CI by checking the test output for any Sentry-related warnings.Missing regression test
There is no test that verifies
handleErroris correctly exported from both hooks files after the integration. A type-checking test (npm run check) will catch this at the TypeScript level, making it an acceptable quality gate.Recommendations
page.on('consoleMessage', ...)— assert no console errors occur on page load whenVITE_SENTRY_DSNis absent from the test environment.npm run checkpasses with zero errors after the integration — this is the most practical automated gate for hooks type correctness.VITE_SENTRY_DSNset.VITE_SENTRY_DSN=(empty) to any.env.testor Vitest setup config to make the "disabled" behavior explicit in CI.🎨 Leonie Voss — UX Designer & Accessibility Strategist
Observations
This is a pure infrastructure/observability issue with zero user-facing UI changes. No components, routes, or interaction patterns are added or modified.
From a UX perspective, the only relevant question is whether error reporting could inadvertently affect the user experience when GlitchTip is unavailable or slow — and the issue correctly scopes this to a write-only async call that should not block rendering.
The
enabled: !!import.meta.env.VITE_SENTRY_DSNguard ensures the integration is completely invisible to users in environments without a DSN, which is the correct default for a dev/CI environment.No accessibility, responsive design, or brand compliance concerns are raised by this issue.
Recommendations
@sentry/sveltekitbrowser SDK uses a non-blocking transport by default — verify this is still the case in the installed version.⚙️ Tobias Wendt — DevOps & Platform Engineer
Observations
Missing
VITE_SENTRY_DSNin.env.exampleI checked the root
.env.example— it does not contain aVITE_SENTRY_DSN=entry. The issue states "confirm this line exists" but it does not. This is a concrete gap that must be addressed in the PR.CI environment
The CI workflow (
.gitea/workflows/ci.yml) does not setVITE_SENTRY_DSN. This is correct — CI should not have an active DSN. Theenabled: !!import.meta.env.VITE_SENTRY_DSNguard ensures CI builds are unaffected. Verify this explicitly in the PR by checking that the CI build log contains no Sentry initialization messages.Self-hosted catalogue
docs/infrastructure/self-hosted-catalogue.mdalready documents GlitchTip with a Docker Compose snippet. The Compose snippet usesimage: glitchtip/glitchtip:latest— this violates the pinned-tag convention. That pre-existing issue is out of scope for this PR, but worth noting for the GlitchTip infrastructure issue.Source map upload in production builds
If the
@sentry/sveltekitVite plugin is added (for source map upload), it will require aSENTRY_AUTH_TOKENenvironment variable duringnpm run build. This must be handled in the production build pipeline, not in CI test runs. The release workflow (.gitea/workflows/release.yml) would need this secret. Confirm whether source map upload is in scope for this issue or deferred to a follow-up.Package lock file
The issue correctly lists
package-lock.jsonas a required update. Confirm that the lockfile is committed alongsidepackage.json— both must be in the same commit to avoid "lockfile out of sync" errors on the nextnpm ci.Recommendations
VITE_SENTRY_DSN=to.env.exampleas part of this PR — the issue says "confirm this line exists" but it doesn't. This is a required deliverable.@sentry/sveltekitVite plugin (source map upload) in this issue unless aSENTRY_AUTH_TOKENsecret is already configured in Gitea secrets. Keep the scope to SDK init +handleErroronly. Source map upload can be a follow-up once the GlitchTip project is established.docs/infrastructure/self-hosted-catalogue.mdthat the SvelteKit app usesVITE_SENTRY_DSNto connect to GlitchTip — so operators know to set it when standing up the stack.📋 Elicit — Requirements Engineer
Observations
Acceptance criteria completeness
The six ACs are well-structured and cover the key behaviors. However, two ACs have verifiability gaps:
Scope boundary
The issue scope is
@sentry/sveltekitonly. Backend Spring Boot error reporting (Sentry Java SDK) is out of scope and not mentioned — this is appropriate. However, the "Definition of Done" should clarify that GlitchTip will only receive frontend errors after this issue, and backend errors require a separate issue.Missing non-functional requirements
The issue does not state:
tracesSampleRate: 1.0is the intended production setting or a placeholderDefinition of Done vs Acceptance Criteria overlap
The DoD largely repeats the ACs. "All acceptance criteria checked" is self-referential. The DoD should add conditions not already in the ACs, e.g.: "No performance regression observed in
npm run buildoutput size."Recommendations
tracesSampleRate: state whether1.0is the intended production value or a development placeholder. If it is production-intended, document the rationale.🗳️ Decision Queue — Action Required
3 decisions need your input before implementation starts.
Observability / Configuration
tracesSampleRate:1.0vs0— Setting1.0enables full performance tracing (every request sent to GlitchTip), while0captures errors only. For a family archive focused on error visibility rather than APM,0avoids noise and reduces outbound traffic. If you want performance traces from day one, keep1.0and accept the extra GlitchTip event volume. (Raised by: Markus, Nora)Security / Privacy
beforeSend— TheAppUserobject (email, groups) fromevent.locals.usermay end up in GlitchTip error reports. Even on a self-hosted instance, you may not want user data in error logs. Options: (a) add abeforeSendhook that deletesevent.user, (b) accept the exposure because GlitchTip is self-hosted and family-only, (c) defer to a hardening follow-up. (Raised by: Nora)Scope
@sentry/sveltekitVite plugin and aSENTRY_AUTH_TOKENsecret in the release pipeline. Options: (a) descope AC 6 / accept minified stack traces for now and do source maps in a follow-up issue, (b) extend this issue to include the Vite plugin + secret setup. Tobias recommends option (a) to keep this PR minimal. (Raised by: Nora, Tobias, Elicit)Implementation complete. PR #591 opened: #591
What was done:
@sentry/sveltekit(94 transitive deps added)frontend/src/hooks.client.ts— new file withSentry.init()andhandleError = Sentry.handleErrorWithSentry()for browser-side error capturefrontend/src/hooks.server.ts— addedSentry.init()block at the top andhandleError = Sentry.handleErrorWithSentry()at the bottom; the existinghandle = sequence(userGroup, handleAuth, handleLocaleDetection, handleParaglide)is preserved intactVITE_SENTRY_DSNwas already in.env.examplefrom the scaffold issue — no change neededGating:
enabled: !!import.meta.env.VITE_SENTRY_DSN— when the env var is unset (the default), Sentry is fully disabled and no requests are made.Validation:
npm run buildpasses cleanly.npm run checkshows no errors in the hooks files (pre-existing type errors in other files are unrelated).✅ Implementation complete — merged via PR #591
What was implemented:
frontend/src/hooks.client.ts— new file; browser-side Sentry error capture withSentry.init()gated onVITE_SENTRY_DSNpresencefrontend/src/hooks.server.ts— SSRhandleErrorviaSentry.handleErrorWithSentry(); existinghandlesequence untouchedfrontend/vite.config.ts— addedsentrySvelteKit()as first Vite plugin withautoUploadSourceMaps: !!process.env.SENTRY_AUTH_TOKEN(skips upload when token absent in CI)frontend/package.json—@sentry/sveltekit: ^10.53.1added todependencies.env.example—SENTRY_AUTH_TOKEN=stub addedCI note: Backend Unit Tests failed due to surefire RAM timeout (25 min, known flaky infrastructure issue) — no Java code in this PR; all frontend/TypeScript jobs passed.
Commits:
b4e6e4ca,00a8731c