feat: Add GlitchTip error monitoring with Sentry SDK

- Install @sentry/nextjs
- Add sentry.client.config.ts, sentry.server.config.ts, sentry.edge.config.ts
- Conditional initialization based on GLITCHTIP_DSN env variable
- Add /api/glitchtip-tunnel route to bypass ad blockers
- Update next.config.mjs with withSentryConfig wrapper
- Integrate Sentry.captureException in error.tsx and global-error.tsx
- Support env vars: GLITCHTIP_DSN, NEXT_PUBLIC_GLITCHTIP_DSN, GLITCHTIP_URL, etc.
This commit is contained in:
2026-01-18 21:33:59 +01:00
parent 489b975911
commit ef2b9dfabf
10 changed files with 1880 additions and 17 deletions

View File

@@ -0,0 +1,56 @@
import { NextRequest, NextResponse } from 'next/server';
/**
* GlitchTip/Sentry Tunnel API Route
*
* This tunnels error reports from the client through our own API,
* bypassing ad blockers that might block direct Sentry/GlitchTip requests.
*/
export async function POST(request: NextRequest) {
const dsn = process.env.NEXT_PUBLIC_GLITCHTIP_DSN;
if (!dsn) {
return NextResponse.json(
{ status: 'error', message: 'GlitchTip not configured' },
{ status: 503 }
);
}
try {
// Parse the DSN to extract components
// DSN format: https://<key>@<host>/<project_id>
const dsnUrl = new URL(dsn);
const key = dsnUrl.username;
const host = dsnUrl.host;
const projectId = dsnUrl.pathname.replace('/', '');
const glitchtipUrl = `https://${host}/api/${projectId}/envelope/?sentry_version=7&sentry_key=${key}&sentry_client=sentry.javascript.nextjs`;
const body = await request.text();
const response = await fetch(glitchtipUrl, {
method: 'POST',
headers: {
'Content-Type': 'text/plain;charset=UTF-8',
'Accept': '*/*',
},
body: body,
});
if (!response.ok) {
console.error('[GlitchTip Tunnel] Error:', response.status, await response.text());
return NextResponse.json(
{ status: 'error', message: 'Failed to forward to GlitchTip' },
{ status: response.status }
);
}
return NextResponse.json({ status: 'ok' });
} catch (error: any) {
console.error('[GlitchTip Tunnel] Error:', error);
return NextResponse.json(
{ status: 'error', message: error.message },
{ status: 500 }
);
}
}

View File

@@ -2,6 +2,7 @@
import { useEffect } from 'react';
import { AlertTriangle, RefreshCcw } from 'lucide-react';
import * as Sentry from "@sentry/nextjs";
export default function Error({
error,
@@ -12,8 +13,11 @@ export default function Error({
}) {
useEffect(() => {
console.error('App Crash Error:', error);
// Report error to Sentry/GlitchTip
Sentry.captureException(error);
}, [error]);
return (
<div className="flex min-h-screen flex-col items-center justify-center p-6 bg-zinc-50 dark:bg-black text-center">
<div className="bg-white dark:bg-zinc-900 p-8 rounded-3xl border border-zinc-200 dark:border-zinc-800 shadow-xl max-w-md w-full space-y-6">

View File

@@ -1,6 +1,8 @@
'use client';
import { RefreshCcw } from 'lucide-react';
import * as Sentry from "@sentry/nextjs";
import { useEffect } from "react";
export default function GlobalError({
error,
@@ -9,6 +11,11 @@ export default function GlobalError({
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// Report error to Sentry/GlitchTip
Sentry.captureException(error);
}, [error]);
return (
<html lang="de">
<body>

View File

@@ -227,14 +227,14 @@ export default function SessionsPage() {
<div
key={session.id}
className={`p-4 bg-zinc-900 rounded-2xl border transition-all group ${activeSession?.id === session.id
? 'border-orange-600/50'
: 'border-zinc-800 hover:border-zinc-700'
? 'border-orange-600/50'
: 'border-zinc-800 hover:border-zinc-700'
}`}
>
<div className="flex items-center gap-4">
<div className={`w-12 h-12 rounded-xl flex items-center justify-center ${session.ended_at
? 'bg-zinc-800 text-zinc-500'
: 'bg-orange-600/20 text-orange-500'
? 'bg-zinc-800 text-zinc-500'
: 'bg-orange-600/20 text-orange-500'
}`}>
<GlassWater size={24} />
</div>
@@ -256,7 +256,7 @@ export default function SessionsPage() {
</div>
{session.participants && session.participants.length > 0 && (
<AvatarStack names={session.participants} maxShow={3} size="sm" />
<AvatarStack names={session.participants} limit={3} size="sm" />
)}
<div className="flex items-center gap-1">