A deploy that succeeds tells you the build compiled and the process started. Whether the site loads, the database answers, and checkout still works are separate facts, and by default nobody is checking any of them. The usual way a solo developer finds out is an email from the one user who cared enough to write.
The idea
Monitoring means something outside your app asks it a question on a schedule, and shouts at a human when the answer changes. Two kinds cover almost everything a small project needs: an uptime check that asks "are you alive?" from the outside, and an error tracker that reports exceptions from the inside. Both categories have usable free tiers.
OUTSIDE YOUR APP INSIDE
------- -------- ------
uptime check --- GET /api/health ---> 200 OK
| | db.query('select 1')
| 2 failures in a row | storage ping
v |
email + push to you exception thrown
|
v
error tracker ---> 41 users hit thisHow it works
A Health check endpointA URL your app exposes whose only job is to answer "am I working?" It checks its own dependencies and returns a success or failure code.
is a route like /api/health that does more than return "ok". A useful one runs a trivial query
against the database, then returns 200 if everything answered and 500 if anything did not. Two
properties matter as much as the query, and an AI will not add them unless you ask:
- It must not be cached. A proxy or CDN that caches the 200 keeps serving it to your monitor while the database is down, which is the failure this route exists to catch.
- It must return a status code and nothing else. A database connection error routinely names the host, database and username, and this route is public because the monitor has to reach it.
An uptime service (UptimeRobot, Better Stack and Cronitor all have free tiers; Cloudflare has its own health checks, but those start at its Pro plan) requests that URL every few minutes from somewhere that is not your machine, and notifies you on a failure or a timeout.
Read your plan before relying on any of it. On UptimeRobot's free tier the interval is five minutes, alerts arrive by email and mobile push, and SMS costs extra, so a phone number typed into the form is not by itself a text at 4am. Delaying an alert until the second consecutive failure, which is what stops one blip waking you, is also paid there (checked 2026-08-12). Free usually means the first failure or nothing, so expect some false alarms.
An error tracker (Sentry, Bugsnag and Rollbar are the same category; the examples below use Sentry) is a small library you install into the app itself. When an exception is thrown in production it sends the stack trace, the URL, the browser, and a count of how many people hit it. That last number is the one you cannot get any other way, and it separates a typo one person found from a bug affecting everyone since Tuesday.
What to do
-
Add
/api/healthand make it query the database. One that returns a hardcoded{ok: true}will happily report success while your data layer is on fire. The prompt that gets a usable one:codeAdd a /api/health route that runs SELECT 1 against the database. Return 200 with no body on success and 500 with no body on failure. Never include the error message in the response. Set Cache-Control: no-store and make sure it is not statically prerendered.Then deploy it and read the response headers back:
curl -sI https://myapp.com/api/health. You are looking for200andcache-control: no-store. -
Point a monitor at it, set the interval to five minutes, and turn on a channel that reaches you away from your desk. Install the service's mobile app and send yourself a test notification rather than assuming the phone number field did it. Ten minutes, and the highest-value thing in this section.
-
Install an error tracker, then confirm an alert reaches you without breaking the live site. Do not add a
throwto a page a user can load. The SDKs send an event on demand instead, so ask for a temporary route that does only that:codeAdd a route at /api/alert-test that calls the Sentry SDK's captureException with a test error and returns 200. No throw, nothing user-facing. I will delete it after one request.Deploy it, load it once yourself, wait for the notification, then delete the route and deploy again. It alerts because the issue is new, so hitting the route a second time will not notify you again. Alerts fail to arrive more often than you would think, usually a spam filter or a notification setting nobody looked at.
-
Renew what expires. A TLS certificate that stops renewing and a domain on a dead credit card both take a working site dark without any code changing. Turn on auto-renew, check the card on file, and point a monitor at the site over
httpsso an expired certificate registers as an outage.
Where it breaks
Uptime checks only prove the URL they request. A monitor pointed at your homepage will stay green while signup, payments and email are all broken, because none of those is on the homepage. Point checks at the paths that touch your dependencies.
Alerts decay. Three flapping monitors that cry wolf every week will teach you to ignore the emails, and then you have monitoring with none of the benefit. Delete a noisy monitor rather than muting it.
Finally, error trackers see your users' data, and the setup wizard is not the private option. Open
the files it generated and read them. For Sentry's Next.js SDK that is instrumentation-client.ts
for the browser, where replaysSessionSampleRate above 0 means real user sessions are being
recorded, and sentry.server.config.ts, where a dataCollection block (older versions:
sendDefaultPii) controls whether request bodies, headers and cookies are attached. Set both
deliberately. These names move between major versions (checked 2026-08-12), so read the reference for
the version you installed.