Skip to content

feat: self-hosted error monitoring (client + server errors, admin Errors tab) - #173

Merged
snackman merged 1 commit into
masterfrom
feat/error-monitoring
Oct 1, 2026
Merged

snackman merged 1 commit into
masterfrom
feat/error-monitoring

Conversation

@snackman

@snackman snackman commented Oct 1, 2026

Copy link
Copy Markdown
Owner

Summary

Lightweight error monitoring with no third-party account. Client and server errors are aggregated by fingerprint into a new Supabase table app_errors and shown in a new admin Errors tab.

Server: src/instrumentation.ts onRequestError logs one structured line ([server-error] {route, routeType, method, path, digest, message, stack}, visible in Vercel logs) and records the error. The write is best effort, capped at 1.5s, and never throws. Next.js control-flow digests (NEXT_*) are skipped.

Client: src/instrumentation-client.ts (supported in Next 16) captures error and unhandledrejection. error.tsx (UI unchanged) and a new global-error.tsx report boundary errors. Reports go through createTrackingQueue to POST /api/errors. The client:

  • dedupes per session by message + stack head
  • sends at most 10 reports per page session
  • drops noise: ResizeObserver loop, extension URLs, Script error. with no stack, aborts and network failures

API /api/errors: accepts a single report or {events:[...]} (max 20) through parseBatch. Fields are zod-validated and truncated (message 500, stack 4000, url 500, UA 300). Bodies over 32KB get a 413. Each report goes to record_app_error(), keyed by sha1(source + message + first stack frame), and duplicates inside one batch are merged into a single call. If the table or function isn't deployed yet, the route returns 202 and calls console.warn.

Migration (NOT applied): supabase/migrations/20261001120000_app_errors.sql

  • table with a unique fingerprint
  • RLS enabled with no policies (service role only)
  • last_seen desc index
  • atomic upsert function record_app_error: SECURITY DEFINER, pinned search_path, EXECUTE only for service_role. A repeat of a resolved error reopens it.

Admin: Errors tab with unresolved/resolved/all and source filters, expandable stack/URL/UA, and resolve/reopen buttons (single or bulk). Backed by GET/POST /api/admin/errors with password auth through isAdminPassword.

Before / after merge

  • Apply the migration.
  • Add a Vercel rate-limit (WAF) rule for POST /api/errors. It is public and unauthenticated.

Testing

  • npx tsc --noEmit, npm test (307 passed, including new error-monitoring.test.ts), npx next build, eslint on the new files
  • next dev + Playwright: uncaught errors and a rejection thrown from the page were batched to /api/errors, and the ResizeObserver noise was filtered out. The route returned 202 (table missing). curl checks: 413 for an oversized body, 400 for bad JSON, 401 for a bad admin password. A temporary throwing route produced the [server-error] log line.

🤖 Generated with Claude Code

…min Errors tab)

- instrumentation.ts onRequestError: [server-error] structured log line +
  best-effort, time-boxed record into app_errors
- instrumentation-client.ts: window error/unhandledrejection capture;
  error.tsx + new global-error.tsx report boundary errors
- Client reporter batches via createTrackingQueue to POST /api/errors;
  per-session dedupe (message+stack head), cap 10, noise filter
- /api/errors: single or {events:[...]}, zod + truncation, 32KB cap,
  aggregates by sha1 fingerprint via record_app_error(); 202 when the
  table/function is not deployed yet
- Migration 20261001120000_app_errors.sql (not applied): table, RLS
  (service role only), last_seen index, atomic upsert function
- Admin Errors tab + GET/POST /api/admin/errors (list/filter/resolve)
- Tests for fingerprinting, noise filtering, schema, reporter

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
sheeets Ready Ready Preview Oct 1, 2026 11:32am UTC

Request Review

@snackman
snackman merged commit dc12c54 into master Oct 1, 2026
3 checks passed

This branch was successfully deployed

1 active deployment
Preview — 9caa41f3 Deployed Oct 1, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant