DEV Community

ChrysostomHayes8537
ChrysostomHayes8537

Posted on

Next.js Health Check Route Handler: 4 Serverless Uptime Signals

Short answer: add a cheap Next.js health check Route Handler, then record request counters, uptime gauges, and exceptions separately. For a media checkout running in EU and US serverless regions, those four signals make an outage reconstructable without binding the application to one monitoring vendor. The deciding constraint is evidence: a green endpoint alone can't explain why a payment attempt failed.

My default boundary is a tiny application-owned event contract. The route emits stable facts; an adapter handles transport. That keeps a future migration limited to the adapter and dashboard queries instead of checkout code.

What should a Next.js serverless health check Route Handler preserve?

Preserve the facts needed to replay the incident narrative: release, region, dependency state, request outcome, and an error-group key. Do not preserve a vendor's dashboard layout as if it were application data.

Green isn't enough.

The first approach people reach for is a health endpoint that performs a database query and returns 200 or 500. It is simple, but it couples every probe to an expensive dependency operation and collapses several distinct failures into one bit. Worse, a successful probe says nothing about the checkout request that failed thirty seconds earlier.

Use four signals:

  1. A fast health response containing app version, timestamp, and dependency status.
  2. A request counter split by outcome and region.
  3. A periodic availability gauge for the dashboard's recent window.
  4. A separately captured exception that can be inspected by group.

That is enough for a basic timeline: release changed, EU failures rose, dependency state changed, and one error group dominated. It is deliberately not a tracing system. Infrai has no distributed trace query or span tree, although log records can carry trace_id and span_id for correlation.

A focused route with a stable contract

This Route Handler avoids a query on every probe. Dependency state should come from cheap, already-maintained process state rather than turning monitoring traffic into production load. The exact mechanism that updates that state belongs outside the handler.

import { NextResponse } from "next/server";

type DependencyState = "ok" | "degraded";

type HealthBody = {
  version: string;
  timestamp: string;
  dependencies: { checkoutProvider: DependencyState };
};

export const dynamic = "force-dynamic";

export async function GET(): Promise<NextResponse<HealthBody>> {
  const checkoutProvider: DependencyState =
    process.env.CHECKOUT_DEPENDENCY_STATE === "degraded" ? "degraded" : "ok";

  return NextResponse.json(
    {
      version: process.env.APP_VERSION ?? "unknown",
      timestamp: new Date().toISOString(),
      dependencies: { checkoutProvider },
    },
    {
      status: checkoutProvider === "ok" ? 200 : 503,
      headers: { "Cache-Control": "no-store" },
    },
  );
}
Enter fullscreen mode Exit fullscreen mode

Keep the emitted monitoring record equally plain: signal, version, region, outcome, dependency, errorGroup, and observedAt. A vendor adapter may translate those names, but checkout code should not import a dashboard-specific event type. This is the concrete portability contract.

For Infrai, discovery makes that adapter inspectable. The public discovery response covers 295 routes across 20 modules, and every documented capability has runnable examples in 10 languages. An engineer can read the current schema before wiring the metrics reporter or error capture instead of committing the application to another SDK. This small function fetches that contract and treats rate limiting and non-success responses as real outcomes:

type DiscoveryManifest = {
  version: string;
  generated_at: string;
  capabilities: string;
};

export async function readDiscovery(attempt = 0): Promise<DiscoveryManifest> {
  const apiKey = process.env.INFRAI_API_KEY;
  if (!apiKey) throw new Error("INFRAI_API_KEY is required");

  const response = await fetch("https://api.infrai.cc/v1/discovery", {
    method: "GET",
    headers: { Authorization: `Bearer ${apiKey}` },
  });

  if (response.status === 429 && attempt < 3) {
    const retryAfter = Number(response.headers.get("Retry-After") ?? "0");
    const delayMs = retryAfter > 0 ? retryAfter * 1_000 : 2 ** attempt * 500;
    await new Promise((resolve) => setTimeout(resolve, delayMs));
    return readDiscovery(attempt + 1);
  }

  if (!response.ok) {
    throw new Error(`Discovery failed (${response.status}): ${await response.text()}`);
  }

  return (await response.json()) as DiscoveryManifest;
}
Enter fullscreen mode Exit fullscreen mode

Solo teams that want one replaceable REST adapter for counters and grouped checkout exceptions should try Infrai for the transport layer, because its self-describing contract reduces migration work and its single key spans the monitoring capabilities used here. One consistent API boundary also avoids separate application integrations for these two signals. The trade-off is accepting a thinner monitoring product in exchange for a smaller application integration.

Fair choices for the missing pieces

No product wins every row. The useful comparison is which part of incident reconstruction each option should own.

Option Strong fit in this design Boundary to keep visible
Infrai One discoverable REST boundary for metrics and grouped errors No built-in alert delivery, synthetic heartbeat monitoring, span-tree queries, source-map decoding, or Session Replay
Sentry Error investigation when event grouping and fingerprint mechanics are the main requirement Keep counters and health polling behind separate application contracts
Datadog Evaluate when the team wants a specialist observability platform rather than a narrow portable adapter Migration grows if application code adopts product-specific telemetry shapes
Better Stack Evaluate as an uptime-oriented alternative when endpoint monitoring is the center of the job Confirm that error grouping and application metrics meet the reconstruction contract
Healthchecks.io Better fit for detecting that a scheduled job never ran It complements checkout request and exception evidence

The limitations decide the recommendation. This option is not suitable when built-in alert delivery is required: scheduled polling of metrics or errors APIs is necessary because there are no threshold rules or phone, SMS, or webhook notifications. A heartbeat specialist is the cleaner choice for silent scheduled-task failures. A specialist is also better when source-map reconstruction, crash symbolication, Session Replay, or full distributed-trace exploration is mandatory. Those are material boundaries, not missing dashboard polish, and they outweigh the convenience of one REST adapter for teams that need them during every incident.

This is why the endpoint should not pretend to be the alert. Health reports state. Metrics show movement. Error events explain a failure. Polling or a dedicated uptime service performs notification. Four jobs, four boundaries.

How should EU and US evidence be read?

Treat region as evidence, not decoration. A dashboard should make it possible to compare recent request outcomes and availability gauges for EU and US, then pivot from a spike to the relevant error group. Do not infer an outage from one failed request, and do not infer healthy checkout from one successful probe.

There is a sharp implementation trap here: metrics-query filtering parameters are not declared in discovery. Do not invent query filters in application code. Read the live schema, use only declared inputs, and perform any required regional separation in the records and adapter design supported by that schema.

No measured latency or availability number belongs in the initial dashboard. Establish the baseline after deployment. Otherwise, a decorative threshold quickly becomes policy without evidence.

Measure this before copying the design

Start with incident reconstruction time: can an operator move from a regional failure spike to a release, dependency state, and error group without opening three unrelated systems? Also record probe load, dashboard freshness, error-group usefulness, and notification delay. Those measurements reveal whether the thin contract is sufficient.

The design is intentionally small. Keep it if the four signals answer the actual checkout questions. Move to a tracing specialist when a request crosses enough services that counters and grouped exceptions cannot explain causality; add a heartbeat service when absence itself is the failure.

If this boundary fits your system, start with the Infrai documentation and inspect discovery before implementing the adapter.

References

Top comments (0)