feat(observability): add Sentry error monitoring (@sentry/nextjs)

Server, edge, and client instrumentation wired up via SENTRY_DSN /
NEXT_PUBLIC_SENTRY_DSN. Fully inert without a DSN configured (verified
with a clean local build + full test suite) — safe to ship ahead of
actually having a Sentry project. Source-map upload is opt-in via
SENTRY_AUTH_TOKEN (kept out of the Docker build-arg chain since build
args land in image layer history; only the public DSN is a build arg).
This commit is contained in:
admin committed 2026-08-10 12:57:42 +02:00
1 parent f48d98cf3a
commit f559b3d68e
8 files changed
+1976 -62

No files matched your search

+13
View File
@@ -61,3 +61,16 @@ REDIS_URL="redis://localhost:6379"
# Set to "false" to disable the invite requirement after the Alpha/Beta phase.
# Default: "true" (invite required — enforced in proxy.ts)
INVITE_REQUIRED="true"
# ── Error Monitoring (Sentry) ───────────────────────────────────
# Sentry Dashboard -> Settings -> Projects -> <project> -> Client Keys (DSN)
# Same DSN value in both — server-side (src/instrumentation.ts) and
# client-side (src/instrumentation-client.ts) report to the same project.
# Leave unset to run with Sentry fully inert (no build-time requirement).
SENTRY_DSN="https://xxxxxxxxxxxxxxxxxxxx@o0.ingest.sentry.io/0"
NEXT_PUBLIC_SENTRY_DSN="https://xxxxxxxxxxxxxxxxxxxx@o0.ingest.sentry.io/0"
# Optional — only needed to upload source maps for readable stack traces.
# Sentry Dashboard -> Settings -> Auth Tokens (needs project:releases scope).
SENTRY_ORG="your-sentry-org"
SENTRY_PROJECT="your-sentry-project"
SENTRY_AUTH_TOKEN=""
+2
View File
@@ -21,9 +21,11 @@ COPY . .
# All server-side secrets are injected at runtime via docker-compose env_file.
ARG NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
ARG NEXT_PUBLIC_SUPABASE_URL
ARG NEXT_PUBLIC_SENTRY_DSN
ENV NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=$NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
ENV NEXT_PUBLIC_SUPABASE_URL=$NEXT_PUBLIC_SUPABASE_URL
ENV NEXT_PUBLIC_SENTRY_DSN=$NEXT_PUBLIC_SENTRY_DSN
ENV NEXT_TELEMETRY_DISABLED=1
RUN npx prisma generate
+1
View File
@@ -16,6 +16,7 @@ services:
# Docker Compose reads these from docker/.env automatically.
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: ${NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY}
NEXT_PUBLIC_SUPABASE_URL: ${NEXT_PUBLIC_SUPABASE_URL}
NEXT_PUBLIC_SENTRY_DSN: ${NEXT_PUBLIC_SENTRY_DSN}
image: pawfeed:latest
container_name: pawfeed
restart: unless-stopped
+11 -1
View File
@@ -1,5 +1,6 @@
import type { NextConfig } from "next";
import createNextIntlPlugin from "next-intl/plugin";
import { withSentryConfig } from "@sentry/nextjs";
const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts");
@@ -77,4 +78,13 @@ const nextConfig: NextConfig = {
},
};
export default withNextIntl(nextConfig);
export default withSentryConfig(withNextIntl(nextConfig), {
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
silent: true,
// No auth token configured on this self-hosted deploy yet — skip
// source-map upload rather than failing the Docker build.
sourcemaps: { disable: !process.env.SENTRY_AUTH_TOKEN },
disableLogger: true,
});
+1922 -60
View File
File diff suppressed because it is too large. Load diff
+2 -1
View File
@@ -19,6 +19,7 @@
"@mux/mux-player-react": "^3.13.0",
"@prisma/adapter-pg": "^7.8.0",
"@prisma/client": "^7.8.0",
"@sentry/nextjs": "^10.69.0",
"@supabase/supabase-js": "^2.107.0",
"@tanstack/react-query": "^5.101.0",
"@trpc/client": "^11.17.0",
@@ -46,7 +47,6 @@
},
"devDependencies": {
"@tailwindcss/postcss": "^4",
"shadcn": "^4.10.0",
"@types/node": "^20",
"@types/pg": "^8.20.0",
"@types/react": "^19",
@@ -56,6 +56,7 @@
"eslint": "^9",
"eslint-config-next": "16.2.7",
"jsdom": "^28.1.0",
"shadcn": "^4.10.0",
"tailwindcss": "^4",
"tsx": "^4.22.4",
"typescript": "^6.0.3",
+6
View File
@@ -0,0 +1,6 @@
import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
tracesSampleRate: 0.1,
});
+19
View File
@@ -0,0 +1,19 @@
import * as Sentry from "@sentry/nextjs";
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 0.1,
});
}
if (process.env.NEXT_RUNTIME === "edge") {
Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 0.1,
});
}
}
export const onRequestError = Sentry.captureRequestError;