Day 60: We wired up error monitoring before anyone could find a bug
Before today, if something broke in Ominvo at 3:30 in the morning, we had no idea. The site could be returning errors to real users — API routes crashing, server components throwing, the Stripe webhook failing silently — and we'd find out when someone emailed. That's not acceptable, even at zero MRR. The first impression a paying customer gets is the one that sticks. We can't be the last ones to know.
Today we fixed that.
Sentry is now wired into every layer of the stack. One package, three config files, and an instrumentation hook — and every unhandled error in the app now sends an email with the exact file, the exact line number, and the full stack trace. Not "something went wrong on the server." Not a vague 500 log buried in Vercel. Exactly what threw, exactly where, with the request context attached.
The coverage spans all three Next.js runtimes: client (browser JavaScript), server (API routes and server components), and edge (middleware). Each one has its own Sentry init with matching config. If a user hits an error on the dashboard, we know before they close the tab.
Three decisions we made while setting this up.
Sampling rate at 10%, not 100%. On the free Sentry tier, 100% trace sampling burns quota fast. 10% catches real patterns without exhausting the budget during beta. Errors are always captured regardless of the sample rate — sampling only affects performance traces.
PII disabled. User emails, IP addresses, and session data don't leave our server and don't appear in Sentry. The flag is sendDefaultPii: false. It's a one-liner, and it's the right default when you're building a product that small business owners will trust with their customer data.
DSN in environment variables, never in source code. SENTRY_DSN on the server, NEXT_PUBLIC_SENTRY_DSN on the client. If the key ever needs to be rotated, it's one line in Vercel's dashboard, not a commit and redeploy.
The second thing we built today is /api/health. It's a single endpoint that checks Supabase, Stripe, and Anthropic on every call. If all three respond without error, it returns 200 ok. If any one of them fails, it returns 503 degraded with a per-service breakdown — { supabase: "ok", stripe: "error", anthropic: "ok" } — so we know exactly which integration is having a bad day.
UptimeRobot pings it every five minutes. If Stripe goes down while the homepage still loads fine, we'll know within five minutes — not when a customer emails asking why they can't complete a purchase. The distinction matters: Stripe going down doesn't crash the site. It just means nobody can pay. Without a health check, that's invisible until someone tries.
We've been logging this work every day on the changelog. Some days it's features — auth flows, pricing, loading skeletons. Today it's infrastructure nobody will ever see in the UI.
No user will notice Sentry is running. No one will see the health endpoint in their browser. If everything works, both are completely invisible.
But when something breaks — and something will break — we'll know before the customer does. We'll have the file, the line, the stack trace, and a per-service status breakdown, all within five minutes of the error occurring.
That's the only acceptable standard, even before anyone's paying.
Tagged
Written by
The founder of Ominvo
Building review management for single-location small businesses. Join the waitlist →