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
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:
57
apps/web/src/lib/sentry.ts
Normal file
57
apps/web/src/lib/sentry.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user