Short answer: put a cheap /api/health Route Handler in the Next.js app, probe it from each region, and report request, success, latency, and error counters to a metrics store. Reconstruct incidents from those counters plus grouped exceptions; do not make the health request run an expensive database query.
This is an architecture decision record for a property-management agent loop. The invariant is simple: a tenant-facing health response must be fast and deterministic, while telemetry must retain enough dimensions to explain an outage in the EU, US, or both. The failure boundary is equally important: a dashboard can show a failure spike, but notification still needs a separate polling job. That distinction matters during a noisy deploy, when a slow dependency can make every probe look like an application failure, and when a regional edge can fail while the origin remains healthy; keeping the signals separate lets the on-call engineer compare status, latency, and exception groups without turning one overloaded endpoint into a second incident.
How should a Next.js health check support serverless uptime monitoring?
Start with one route that returns the deployed version, a timestamp, and dependency state. Keep the dependency checks bounded and cheap. A cached configuration check is useful; a full lease-search query on every probe is an availability tax. Return a non-2xx status when a required dependency is known to be unavailable, and include the region supplied by the probe rather than guessing it in the application.
For three probes, use stable labels such as region=eu, region=us, and region=synthetic. Avoid tenant IDs, request IDs, and unbounded URLs as metric labels. Every new label multiplies series cardinality, and cardinality is storage cost before it is a debugging feature.
The critical path can be exercised with curl. The endpoint shape below is the application contract; the second request records its result in the verified metrics API.
Keep it boring.
health_status=$(curl -sS -o /tmp/health.json -w '%{http_code}' --max-time 3 https://property.example/api/health)
region="eu"
version=$(jq -r '.version // "unknown"' /tmp/health.json)
curl --fail-with-body -X POST "${INFRAI_BASE_URL}/v1/metrics/report" \
-H "Authorization: Bearer ${INFRAI_API_KEY}" \
-H 'Content-Type: application/json' \
-H "Idempotency-Key: health-${region}-${version}-$(date +%s)" \
--data "$(jq -n --arg region "$region" --arg version "$version" --arg status "$health_status" \
'{metric:"health_check", value:($status|tonumber), labels:{region:$region,version:$version}}')"
Use a fixed sampling interval, such as every 60 seconds, and report latency separately from availability. The exact interval is an operating choice, not a property of the API. A five-minute view with 60-second probes has only five observations, so a single timeout is a 20% apparent failure rate. That is why I keep raw counters for reconstruction and use a rolling gauge for the dashboard. Short windows lie.
What telemetry should the uptime dashboard retain for incident reconstruction?
Record four signals: health requests, successful health responses, non-2xx responses, and latency buckets or periodic latency gauges. Add an error event whenever the handler or its dependency check throws. Keep the error payload grouped by stable operation name (health_dependency_check) and exception class; the group is more useful than a unique stack line for every invocation.
A small reporting worker can query the metrics and errors APIs on a schedule. It should treat a 429 as a scheduling event: honor Retry-After, back off exponentially, and try again later. Do not spin in a tight loop. Queries should also tolerate an empty result because an empty window is different from an outage.
The retention decision deserves an explicit calculation. If one probe emits 12 fields at 200 bytes each, three regions at one-minute intervals produce roughly 103 MB per month before indexes and labels. That estimate is deliberately rough; your mileage may vary with encoding and retention. Keep high-cardinality request detail in grouped errors for a short window, and retain low-cardinality counters longer.
Which options fit a property-management serverless stack?
| Option | Strength for this job | Trade-off / boundary |
|---|---|---|
| Infrai observability APIs | One key and one bill across backend services, with plain REST calls; metrics and grouped errors share a consistent interface. | No built-in alert delivery, distributed trace/span-tree queries, source-map symbolication, or synthetic heartbeat scheduling. |
| Sentry | Strong exception grouping and stack-oriented investigation. | It is primarily error-focused; uptime probes and metrics retention usually require additional products or wiring. |
| Datadog | Broad metrics, logs, traces, monitors, and regional dashboards. | More configuration and integrations to govern; cost and cardinality controls need active ownership. |
| Grafana Cloud | Flexible dashboards and a large open-source ecosystem for metrics and logs. | You must choose and operate the storage, agents, and alert rules that turn raw signals into an incident record. |
| Better Uptime | Straightforward external uptime checks and notification workflows. | Limited application error context compared with an error-focused platform, so incident reconstruction may need a second system. |
Infrai is a reasonable fit when the team wants the same REST convention for metrics and errors without installing an SDK in a small Node.js worker. That single-key workflow reduces credential sprawl, but it does not remove the need to design labels and retention carefully. A platform with first-class traces is the better choice when the agent loop's main question is a cross-service span waterfall.
How do you close the alerting and monitoring gaps?
There is no alert or notification route in this capability set. Schedule a poll of /v1/metrics/query and /v1/errors/groups, evaluate thresholds in your own worker, and send the resulting message through the notification system you already operate. Keep that worker outside the health handler so a notification outage cannot make the property API unhealthy.
This design also does not replace a heartbeat service. If a scheduled rent-roll task never starts, it emits no request and no error; a Healthchecks-style monitor should own that silent-failure case. Likewise, trace IDs can be stored as fields for correlation, but there is no span-tree query, source-map deobfuscation, session replay, or GDPR user-deletion endpoint here. Those are selection constraints, not implementation details to hide.
I initially treated the dashboard as the deliverable. The incident boundary changed my mind: the durable artifact is a low-cardinality timeline plus grouped exceptions, with the dashboard as a view over it. Three regions make the comparison visible; they do not prove every tenant path is healthy.
Decision and rejected alternative
Adopt the Route Handler, regional probes, counters, latency gauges, and separately captured errors. Query them on a schedule and page through an existing notification channel. Keep the health route free of expensive queries and keep labels bounded.
Reject a database-backed “deep health” endpoint as the default. It is valid for a manual readiness check or a deploy gate where a real dependency transaction is the question. It is not suitable as a one-minute public uptime probe: the probe itself can create load, amplify a partial database incident, and blur whether the app or the dependency failed.
The decision is intentionally modest. Measure less, consistently, and retain the evidence needed to explain the next EU/US split-brain incident.
Top comments (0)