50 lines
1.5 KiB
TypeScript
50 lines
1.5 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect } from 'react';
|
|
|
|
/**
|
|
* Client-side Sentry init (Session 10).
|
|
*
|
|
* Manual init rather than the @sentry/nextjs `withSentryConfig`
|
|
* wrapper because that plugin conflicts with standalone output mode
|
|
* (the Coolify production build). Manual init keeps the bundle
|
|
* simple: nothing imported when DSN is unset, lazy import via
|
|
* dynamic import() when it is.
|
|
*
|
|
* Mount once at the root layout — repeated mounts get the
|
|
* Sentry-internal idempotency guard, but we keep the layout single-
|
|
* mount to avoid unnecessary work.
|
|
*/
|
|
export default function SentryInit() {
|
|
useEffect(() => {
|
|
const dsn = process.env.NEXT_PUBLIC_SENTRY_DSN;
|
|
if (!dsn) return;
|
|
let cancelled = false;
|
|
(async () => {
|
|
try {
|
|
const Sentry = await import('@sentry/nextjs');
|
|
if (cancelled) return;
|
|
Sentry.init({
|
|
dsn,
|
|
tracesSampleRate: 0.1,
|
|
// PII posture: don't sweep up IPs / cookies automatically.
|
|
sendDefaultPii: false,
|
|
// Trim heavy integrations we don't need for free-tier volume.
|
|
integrations: (defaults) => defaults.filter((i) => i.name !== 'Replay'),
|
|
beforeSend(event) {
|
|
if (event.user) {
|
|
delete event.user.ip_address;
|
|
delete event.user.email;
|
|
}
|
|
return event;
|
|
},
|
|
});
|
|
} catch {
|
|
// Sentry init failure is never user-facing — degrade silently.
|
|
}
|
|
})();
|
|
return () => { cancelled = true; };
|
|
}, []);
|
|
return null;
|
|
}
|