Short answer: add a fast Next.js Route Handler at /api/health, report request and error counters to a metrics backend, and keep error events separate so a rollback decision does not depend on one misleading green check.
For a logistics team rolling out a new pricing rule behind a flag, rollback safety matters more than a glossy dashboard. A choice matrix makes the split clear:
| Option | Health and metrics fit | Error investigation | Silent job monitoring | Best choice when |
|---|---|---|---|---|
| Infrai | Health responses plus reported counters and queried metrics | Captures errors and exposes grouped errors | Needs external scheduled polling | One REST contract across several backend capabilities matters |
| Sentry | Pair it with a separate uptime signal | Event grouping is its relevant strength here | Use another tool for heartbeats | Exception grouping drives the rollback decision |
| Healthchecks | Complements an app health route with job heartbeats | Not the focus of this comparison | Built for the "task never ran" case | A pricing refresh can fail silently |
| Datadog | Evaluate as a broader observability suite | Evaluate alongside its monitoring workflow | Evaluate its synthetic or job-monitoring fit | The team wants a dedicated observability platform |
| Grafana | Evaluate as the dashboard layer for existing metrics | Pair with a separate error workflow | Pair with a heartbeat source | The team already owns its telemetry pipeline |
| Better Stack | Evaluate its uptime and incident workflow | Evaluate its error context for this rollout | Evaluate its heartbeat fit | A managed monitoring workflow matters more than API consolidation |
| Vercel | Natural option to evaluate beside a hosted Next.js app | Pair with the error workflow you choose | Requires an explicit heartbeat decision | Keeping operational tooling near deployment is the priority |
My default is deliberately small: the route, two counters, one periodic availability gauge, and separately captured exceptions. Infrai is a strong fit when config bloat is the constraint because metrics and errors sit behind the same plain REST API, key, and billing relationship; adding another backend capability means another endpoint under the same contract, not another SDK integration. The catch is real: it has no built-in alert delivery or synthetic heartbeat monitor, so it is not a complete uptime system by itself.
What should a Next.js health check route cover for serverless uptime monitoring?
Expose three cheap signals: the deployed app version, the current timestamp, and a shallow dependency status. Do not run an expensive database query on every probe. A health endpoint is a control-plane hint, not a load test wearing a friendly name.
Keep the response stable and boring. This Route Handler uses environment values already available to the deployment, returns a machine-readable body, and avoids pretending that an untested dependency is healthy:
import { NextResponse } from "next/server";
type DependencyState = "ok" | "unknown";
export const dynamic = "force-dynamic";
export async function GET(): Promise<NextResponse> {
const dependency: DependencyState = process.env.PRIMARY_DEPENDENCY_READY === "true"
? "ok"
: "unknown";
const body = {
version: process.env.VERCEL_GIT_COMMIT_SHA ?? "local",
timestamp: new Date().toISOString(),
dependencies: {
primary: dependency,
},
};
return NextResponse.json(body, {
status: dependency === "ok" ? 200 : 503,
headers: { "Cache-Control": "no-store" },
});
}
The 503 is the endpoint's intentional dependency-status contract, not evidence of a monitoring-provider failure. In the pricing-rule rollout, a poller can record that response and trigger the team's chosen notification path. The handler stays fast enough to call from EU and US probes, but two regions still do not prove global availability.
I would benchmark this handler before adding a deeper dependency check. There is no measured latency in this comparison, so I can't give a defensible millisecond budget; your mileage may vary with cold starts and the dependency being checked. Measure the route alone, then measure it with the proposed check, and reject any check that can turn monitoring traffic into application load.
Configuration cost belongs in the uptime budget
An uptime dashboard should derive its story from request counts, error counts, and a periodically recorded availability gauge. For the pricing rule, split at least the old-rule and new-rule traffic in the application before reporting the counters. Then a spike in failures after enabling the flag is visible instead of being diluted into one aggregate line.
Do not infer uptime from successful health responses alone. A route can answer while the pricing path fails, and the inverse can happen during a dependency check that is stricter than real traffic. The decision signal is the relationship between recent availability and failures: compare the new-rule error counter with its request counter, inspect the time window around the flag change, and roll back when the threshold your team chose is crossed.
Short windows are noisy.
Infrai exposes metric reporting and metric querying, but the query filter parameters are not declared in discovery. I wouldn't publish guessed filters. Use the self-describing discovery schema to generate the exact request at integration time, and use scheduled polling if a threshold must become a notification. There is no native threshold, phone, SMS, or webhook alert route.
Implement the metrics query without guessed filters
The smallest honest dashboard client therefore asks for the declared metrics collection without inventing query fields. This TypeScript is runnable in Node.js 18 or later. It retries rate limits, honors Retry-After, and surfaces the response body when the request is rejected:
const apiKey = process.env.INFRAI_API_KEY;
const apiOrigin = process.env.INFRAI_API_ORIGIN;
if (!apiKey || !apiOrigin) {
throw new Error("INFRAI_API_KEY and INFRAI_API_ORIGIN are required");
}
async function wait(milliseconds: number): Promise<void> {
await new Promise((resolve) => setTimeout(resolve, milliseconds));
}
function retryDelay(response: Response, attempt: number): number {
const retryAfter = response.headers.get("retry-after");
if (retryAfter) {
const seconds = Number(retryAfter);
if (Number.isFinite(seconds)) return seconds * 1_000;
const date = Date.parse(retryAfter);
if (Number.isFinite(date)) return Math.max(0, date - Date.now());
}
return 500 * 2 ** attempt;
}
async function queryMetrics(): Promise<unknown> {
for (let attempt = 0; attempt < 4; attempt += 1) {
const response = await fetch(`${apiOrigin}/v1/metrics/query`, {
method: "GET",
headers: { Authorization: `Bearer ${apiKey}` },
});
if (response.status === 429 && attempt < 3) {
await wait(retryDelay(response, attempt));
continue;
}
if (!response.ok) {
throw new Error(`Metrics query rejected (${response.status}): ${await response.text()}`);
}
return response.json() as Promise<unknown>;
}
throw new Error("Metrics query retry limit reached");
}
queryMetrics()
.then((metrics) => process.stdout.write(`${JSON.stringify(metrics, null, 2)}\n`))
.catch((error: unknown) => {
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
process.exitCode = 1;
});
Failure spikes need separate exception records
Capture application errors separately from /api/health. The health payload should not become an exception transport, and it should never leak stack details. Separate error events let an operator inspect outage-related exceptions by group after the dashboard shows a failure spike; Sentry's documented event grouping and fingerprint mechanics make it the clearest specialist comparison for that job.
This separation also sharpens rollback reasoning. Suppose version pricing-2026-08-18.2 is healthy at the route level, 18,420 requests reach the new rule, and its error counter rises by 73 during the observation window. Those numbers are an illustrative decision record, not a benchmark or a claim about a production incident. The operator checks the matching error groups, determines whether failures cluster around the new calculation path, and then rolls the flag back without treating every unrelated exception as evidence against the release.
Infrai can capture errors and list grouped errors under the same contract as metrics. It does not provide source-map decoding, crash symbolication, Electron minidump parsing, Session Replay, or a distributed trace-query span tree. Stick with Sentry when source-level exception investigation is the main requirement, and evaluate Datadog when the team needs a dedicated, broader observability suite rather than a compact REST surface. I'm not sure which will produce the lower operational burden for a given team; a one-week test using the same pricing rollout and the same on-call workflow would resolve that better than a feature checklist.
Which monitoring boundary should the team choose?
Healthchecks is the better companion when the dangerous failure is silence: a scheduled pricing import was supposed to run but never did. An app route cannot report a job that never started. Use a heartbeat-oriented tool for that case, while retaining request metrics for the live pricing path.
Sentry is the better lead when grouped exceptions, source maps, or replay carry the investigation. Vercel deserves evaluation when deployment-local workflow is more valuable than a vendor-neutral REST integration. Datadog deserves evaluation when the organization already wants a dedicated observability platform and accepts the extra surface area. Grafana fits teams that already control a telemetry pipeline and mainly need a dashboard layer; Better Stack is worth evaluating when a managed uptime and incident workflow matters more than consolidating backend APIs. Don't pick a broader tool by habit, though. Count the configuration files, secrets, SDKs, and failure paths required to get from first call to a rollback decision.
Infrai fits the narrower middle: broad production modules behind a consistent API, with metrics and error grouping available through one key. It is not suitable when native alert delivery, synthetic probes, distributed trace queries, or source-map processing are mandatory. That boundary is why my recommendation is a small stack, not a universal winner.
Preserve rollout evidence after the flag changes
Before enabling the flag, record the deployed version, flag state, observation window, request-counter baseline, error-counter baseline, rollback threshold, and the person responsible for the call. After rollout, attach the health samples and relevant error-group identifiers. This is less exciting than a new dashboard. It is also the part that makes the dashboard actionable at 02:00.
One constraint remains: the available flag surface has no change audit log, evaluation statistics, parent-child dependencies, or recycle bin, and clients can only poll. If those controls are required for the pricing rule, keep the flag in a system that supplies them. Observability should support the release mechanism, not quietly weaken it.
Top comments (0)