feat(observability): browser Sentry SDK (gated on VITE_SENTRY_DSN)
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled

Sase frontend has Faro (RUM) and PostHog (product analytics) but no browser
error monitoring. A recent panel session investigation (panel insight
cmpv9q3ms004dfcphluw0z8bh — silent rage-clicks after parts_panel_viewed
with no API call) couldn't be confirmed or refuted from JS error data
because there was no JS error channel at all. This adds one.

- @sentry/react ^9 (dynamic import in lib/sentry.ts so the bundle only
  pays the SDK cost when DSN is configured)
- Init is gated on VITE_SENTRY_DSN — unset = no-op, no behaviour change
- Replay only fires on error (sessionSampleRate 0, onErrorSampleRate 1)
- KVKK: sendDefaultPii false, maskAllText + blockAllMedia on replay
- Builds emit hidden source maps so Sentry can de-minify traces while
  end users don't fetch the maps in the browser
- docker-compose.coolify.yml threads VITE_SENTRY_* through as build args
  (VITE_* must be build-time; runtime env never reaches a Vite bundle)

Wiring on the Coolify side is a separate manual step — set
VITE_SENTRY_DSN on the prod (ro48g…) and/or dev (jwgwkg…) app and
redeploy. Backend Sentry (NestJS) is unchanged.

Refs: Süper Panel docs/ARCHITECTURE.md, panel sentry-archive job.
This commit is contained in:
Süper Panel
2026-06-01 18:08:32 +03:00
parent 3a3a7d3ebf
commit 9f3eb0ff86
5 changed files with 71 additions and 1 deletions

View File

@@ -22,6 +22,7 @@
"@remotion/player": "^4.0.422",
"@sase/shared": "workspace:*",
"@sase/ui": "workspace:*",
"@sentry/react": "^9.0.0",
"@tanstack/react-query": "^5.62.0",
"@tanstack/react-router": "^1.120.0",
"better-auth": "^1.2.0",

View File

@@ -0,0 +1,57 @@
/**
* Sentry browser SDK init — gated on VITE_SENTRY_DSN.
*
* Co-exists with Faro (Grafana) and PostHog: Sentry handles uncaught JS errors
* and on-error session replay; Faro handles RUM/traces; PostHog handles product
* analytics. No conflict between the three at the global error-handler layer.
*
* KVKK note: `sendDefaultPii: false` and `maskAllText: true` on replay.
*/
import type { init as SentryInit } from "@sentry/react";
let initialized = false;
export async function initSentry() {
if (initialized) return;
const dsn = import.meta.env.VITE_SENTRY_DSN;
if (!dsn) {
if (import.meta.env.DEV) {
console.info("[sentry] VITE_SENTRY_DSN unset — skipping init");
}
return;
}
const environment =
import.meta.env.VITE_SENTRY_ENVIRONMENT ?? import.meta.env.MODE ?? "production";
const release = import.meta.env.VITE_SENTRY_RELEASE;
try {
const Sentry = await import("@sentry/react");
(Sentry.init as typeof SentryInit)({
dsn,
environment,
release,
integrations: [
Sentry.browserTracingIntegration(),
Sentry.replayIntegration({ maskAllText: true, blockAllMedia: true }),
],
// Performance: 10% trace sample rate (low volume site, can raise later).
tracesSampleRate: 0.1,
// Replay: only when an error fires; no session-replay otherwise (cost + privacy).
replaysSessionSampleRate: 0.0,
replaysOnErrorSampleRate: 1.0,
// KVKK: do not auto-attach IP / cookies / user agent fingerprints.
sendDefaultPii: false,
// Drop framework noise that isn't actionable.
ignoreErrors: [
"ResizeObserver loop limit exceeded",
"ResizeObserver loop completed with undelivered notifications.",
"Non-Error promise rejection captured",
],
});
initialized = true;
console.log("[sentry] browser SDK initialized");
} catch (err) {
console.warn("[sentry] init failed:", (err as Error).message);
}
}

View File

@@ -7,9 +7,13 @@ import { initFaro } from "./lib/faro";
import { initLocale } from "./lib/i18n";
import { initMetaPixel } from "./lib/meta-pixel";
import { initPostHog } from "./lib/posthog";
import { initSentry } from "./lib/sentry";
import { routeTree } from "./routeTree.gen";
import "./globals.css";
// Sentry first so it can capture errors thrown by other init code.
initSentry();
// Read persisted locale from localStorage before first render — without this
// a reload always falls back to the TR default even if the user picked EN.
initLocale();

View File

@@ -30,6 +30,9 @@ export default defineConfig({
},
build: {
outDir: "dist",
sourcemap: false,
// 'hidden' emits source maps but strips the //# sourceMappingURL comment
// from JS bundles. Sentry can still consume them (via release upload), but
// browsers won't fetch them, so end-user stack traces stay minified.
sourcemap: "hidden",
},
});