Skip to main content

Error Tracking (Sentry)

Lumio's three Next.js apps — apps/web, apps/admin, apps/id — each ship with a @sentry/nextjs integration. The Rust backend is on sentry-rust via lo-telemetry; this page is about the frontend setup.

When NEXT_PUBLIC_SENTRY_DSN is unset, the integration is a no-op — apps boot normally, no events are sent. Local dev typically leaves the DSN blank; staging and production fill it via Cloudflare Pages env / docker-compose env.

Files per app

Each of apps/{web,admin,id}/ has the same five files:

FileLoaded byPurpose
next.config.tsbuildWraps withSentryConfig (source-map upload, build-time injection)
sentry.client.config.tsbrowserBrowser-side Sentry init + bindSentry for @lumio/logger
sentry.server.config.tsNode runtimeSSR / route-handler / Server-Action capture
sentry.edge.config.tsEdge runtimeEdge route handlers
instrumentation.tsserverBootstrap — picks the right runtime config + binds logger to Sentry

Configuration is duplicated across the three apps on purpose — the apps may evolve independently (different sample rates, different deny-lists). The shared bits are kept identical for now to keep behaviour consistent.

Environment variables

VariableWherePurpose
NEXT_PUBLIC_SENTRY_DSNBrowser + serverPublic DSN. Required to enable client-side capture.
SENTRY_DSNServer onlyOptional — falls back to NEXT_PUBLIC_SENTRY_DSN when unset. Useful when you want the SSR / Edge runtimes on a different project than the browser.
SENTRY_ORGBuild (CI)Sentry org slug — required for source-map upload. Leave blank locally.
SENTRY_PROJECTBuild (CI)Sentry project slug — same.
SENTRY_AUTH_TOKENBuild (CI)Auth token for source-map upload. Generate one in Sentry → Settings → Auth Tokens.
LUMIO_ENV / NEXT_PUBLIC_LUMIO_ENVBothTags every event with the environment (development / staging / production). Falls back to NODE_ENV.

Source-map upload only runs when SENTRY_AUTH_TOKEN + SENTRY_ORG + SENTRY_PROJECT are all set — typically only in CI for staging / production builds.

What gets captured

  • Unhandled exceptions in browser code (React error boundaries, Promise rejections, sync throws).
  • SSR / route-handler errors through the onRequestError export in instrumentation.ts (Sentry's recommended hook).
  • Anything emitted via logger.error(...) or logger.warn(...) — the logger calls Sentry.captureException / Sentry.captureMessage when bound. See Logging for the binding details.

What gets stripped

beforeSend in every runtime config drops the Authorization and Cookie headers from the outgoing event payload. sendDefaultPii: false keeps Sentry's automatic IP / user-agent collection off so cookie-issued JWTs and popout tokens never end up in Sentry.

If you want to enrich events with explicit user info (e.g. account ID), set it manually after login:

import * as Sentry from "@sentry/nextjs";

Sentry.setUser({ id: account.id, ip_address: undefined });

ip_address: undefined opts out of Sentry's IP capture even if sendDefaultPii ever flips on later.

Replays

replayIntegration({ maskAllText: true, blockAllMedia: true }) is enabled with replaysOnErrorSampleRate: 1.0 and replaysSessionSampleRate: 0. Translation: every session is silent until something throws, then Sentry uploads the last ~30 seconds as a replay with all text masked + media blocked.

If you find a streamer needs full-session replay for support purposes, raise replaysSessionSampleRate for that app only — but be aware of GDPR + cost implications. The default (errors-only) is the right baseline.

Performance traces

tracesSampleRate is 0.1 in production (10 % of transactions) and 1.0 everywhere else. Lower it further if your Sentry quota gets stressed; raise it on a feature branch when investigating a specific perf issue.

Suppressed noise

denyUrls blocks the standard browser-extension noise (chrome-extension://, moz-extension://, safari-web-extension://). Add more entries when you see recurring noise from a specific extension or third-party script.

Adding a new captured event manually

Most code should just throw / call logger.error. For business-event tracking (e.g. "user upgraded plan"), use Sentry's addBreadcrumb or captureMessage directly:

import * as Sentry from "@sentry/nextjs";

Sentry.captureMessage("user upgraded plan", {
level: "info",
extra: { account_id: accountId, plan_slug: plan.slug },
});

These don't surface as errors but let you search them in Sentry's "Issues" tab when triaging.

Debugging the integration locally

Set the DSN in .env.local:

NEXT_PUBLIC_SENTRY_DSN=https://<key>@o<org>.ingest.sentry.io/<project>
LUMIO_ENV=development

Then trigger an error from any client component:

"use client";

import { useEffect } from "react";

export default function DebugPage() {
useEffect(() => {
throw new Error("sentry smoketest");
}, []);
return null;
}

Reload the page, open Sentry → Issues, the event should land within a few seconds with environment: development. Filter that out before shipping or the dashboard fills with smoketest noise.