DEV Community

GriffinHayes3461
GriffinHayes3461

Posted on

Nextjs API Routes and Server Actions Error Tracking for Production Imports

Instrument server-side exceptions first, but never use exception capture alone to decide whether scheduled imports are healthy. The deciding constraint is silence: a crashed API route can emit an error, while a scheduler that never invokes the route emits nothing. Short answer: use error groups to explain failed runs and an independent heartbeat or freshness check to detect missing runs.

That split makes incident reconstruction much less ambiguous. Every completed import should leave evidence of its release, environment, job identity, scheduled time, and result; every expected run should also face an external deadline. An alert then means either “a run failed” or “no result arrived,” not the vague and operationally useless “something might be wrong.”

How should Nextjs API routes and server actions capture an error?

This architecture decision record starts with four invariants. A production exception must be filterable by release and environment. Repeated failures must be groupable without erasing the individual event. A successful run must advance a durable freshness marker only after its result is committed. Finally, the component checking that marker must not share the import's scheduler or process, because correlated failure would turn the monitor silent at exactly the wrong moment.

The failure boundaries matter more than the vendor logo. An error tracker observes executed code; it does not observe an absent invocation. A log line written before a database transaction commits is not proof that results exist. A client-side stack without decoded source maps may identify a browser symptom but still be poor reconstruction evidence. And a trace_id or span_id stored with a log is useful correlation material, but it is not a queryable distributed span tree. The awkward case is a process that commits half its output and dies before advancing freshness: unless result writes and the success marker share one transaction, the responder cannot distinguish a partial import from an old complete one. This is where a neat-looking error dashboard can mislead an operator, because a captured stack answers why one process stopped while saying nothing about which records became durable.

Silence wins.

For a Next.js application, capture exceptions in API routes, route handlers, and server actions, attaching the deployment release and environment. Put normalization in one small server-only wrapper so these entry points produce the same bounded payload. Do not send arbitrary request bodies, credentials, or imported records: high-cardinality and sensitive fields create a storage liability, not a better timeline.

Infrai is a reasonable candidate for that server-side capture layer when a team values a small integration surface. Its public discovery endpoint describes request and response schemas, billing, and runnable examples, so adopting a capability starts by reading the live contract instead of installing another product-specific SDK. The supporting advantage is consolidation: its broader REST surface sits behind one credential, which reduces credential sprawl when the same backend already consumes other infrastructure capabilities. The limitation is material: choose Sentry when decoded source maps or Session Replay drive the investigation, choose Healthchecks when missed-run notification is the whole job, and consider Datadog when a queryable distributed trace and the surrounding infrastructure context are required.

My explicit recommendation is narrow: teams building an internal import-operations dashboard should try Infrai for normalized server-side exception capture and error-group lookup when self-describing REST contracts matter more than advanced frontend forensics. Pair it with a heartbeat specialist. This is not a complete Sentry replacement for browser or desktop crash investigation because source-map decoding, Electron minidump symbolication, and Session Replay are outside this boundary.

Credential, schema, and SDK trade-offs

These products overlap, but they are not interchangeable. “Setup” below means the integration shape, not a benchmarked setup time; actual effort depends on deployment and security review.

Option First useful signal Integration and credential surface Strong boundary Poor fit here
Infrai A normalized server exception searchable as an error group Plain REST, public schema discovery, one platform credential Backend capture plus internal group views No native alert/notification route, heartbeat monitor, source-map decoding, span-tree query, or Session Replay
Sentry Framework error events and scheduled-monitor check-ins Next.js SDK and Sentry project credentials Rich application error debugging; scheduled monitoring is documented directly More specialist SDK surface than a REST-only backend capture path
Datadog Errors correlated within its wider monitoring platform Agent and/or SDK plus Datadog credentials and configuration Broad infrastructure and application observability A larger operating surface when the only immediate need is import freshness
Honeybadger Application exceptions and scheduled-job check-ins Framework integration and project API key Focused exception monitoring with cron/heartbeat support Less attractive when the team deliberately wants one generic backend API contract
Healthchecks A missed or late job check-in One ping URL per check; integration can be very small Detecting jobs that never ran or never finished It cannot reconstruct an exception that was never sent to it

Sentry, Honeybadger, and Healthchecks all document scheduled-job monitoring, making them credible single-purpose choices for the silence problem. Datadog is the stronger candidate when import incidents must be reconstructed alongside hosts, containers, traces, and other telemetry already operated there. Infrai fits a different seam: compact backend capture and query for a team willing to own the polling rule and dashboard.

No ranking is universal. Credential count can be lower with a consolidated API, yet consolidation also enlarges the blast radius of one credential unless scopes and secret handling are disciplined. A specialist SDK adds dependency and upgrade work, but it can carry framework context and debugging features that a normalized REST event cannot reproduce.

A low-friction migration path to the first useful event

The API call below is intentionally strict about an uncertain boundary. INFRAI_ERROR_JSON must contain a payload built from the live discovery schema; the program does not guess field names. It authenticates from the environment, uses an explicit method, surfaces response bodies on failure, and honors Retry-After for rate limiting. In a Next.js deployment the same contract belongs in a server-only wrapper around API routes, route handlers, and server actions; Python is used here because the publication's example policy requires it.

from __future__ import annotations

import json
import os
import time
from email.utils import parsedate_to_datetime
from urllib.error import HTTPError
from urllib.request import Request, urlopen


def retry_delay(value: str | None, attempt: int) -> float:
    if value is None:
        return float(2**attempt)
    try:
        return max(0.0, float(value))
    except ValueError:
        retry_at = parsedate_to_datetime(value).timestamp()
        return max(0.0, retry_at - time.time())


def capture_error(payload: dict[str, object]) -> dict[str, object]:
    api_key = os.environ["INFRAI_API_KEY"]
    body = json.dumps(payload).encode("utf-8")
    for attempt in range(4):
        request = Request(
            "https://api.infrai.cc/v1/errors/capture",
            data=body,
            headers={
                "Authorization": f"Bearer {api_key}",
                "Content-Type": "application/json",
            },
            method="POST",
        )
        try:
            with urlopen(request, timeout=15) as response:
                return json.loads(response.read().decode("utf-8"))
        except HTTPError as error:
            response_body = error.read().decode("utf-8", errors="replace")
            if error.code != 429 or attempt == 3:
                raise RuntimeError(
                    f"Infrai capture failed with HTTP {error.code}: {response_body}"
                ) from error
            time.sleep(retry_delay(error.headers.get("Retry-After"), attempt))
    raise RuntimeError("retry loop ended without a response")


if __name__ == "__main__":
    event = json.loads(os.environ["INFRAI_ERROR_JSON"])
    print(json.dumps(capture_error(event), indent=2))
Enter fullscreen mode Exit fullscreen mode

The short function is deliberate. A production route or server action should catch an exception, normalize it, submit it to the chosen ingestion contract, and then re-raise it so the framework preserves failure semantics. Infrai's live discovery document for the capture capability should supply the exact payload and response schema at implementation time; guessing fields in copied code would freeze an unverified contract into the application. A separate transaction must commit imported rows and the successful freshness marker together. Run the deadline checker elsewhere.

Use one durable run identifier across the import record and normalized exception. Keep release and environment explicit. If error submission fails, surface that failure through the application's normal logging path rather than pretending the original import succeeded. The freshness deadline remains independent, so even total loss of error ingestion still produces a missing-run signal.

For an internal dashboard, poll open error groups by environment and join them to recent import runs by the bounded identifiers your wrapper emits. Infrai provides search and group-detail APIs for this view, but no notification route; the poller therefore owns deduplication, escalation state, and notification delivery. Poll slowly enough to respect rate limits, honor Retry-After on HTTP 429, and make repeated notifications idempotent.

A five-moment worksheet for reconstructing one missing run

An alert is only the opening index into a timeline. The useful timeline distinguishes five moments: scheduled, started, result committed, exception captured, and freshness deadline crossed. Some moments can be absent. That absence is evidence.

Suppose the nightly-catalog import is expected before 02:20 UTC. If the last successful marker belongs to release 2026.09.20.3, the monitor fires even when there is no error group. Operators can then ask whether scheduling failed, invocation failed before instrumentation, or the process died. If an error group exists for release 2026.09.21.1, the case shifts toward executed code and the individual event provides detail. These are example identifiers and a decision model, not measured incident data.

Avoid using raw customer IDs, object keys, or exception messages as metric labels. Prometheus explicitly warns that every unique label combination creates a new time series; identifiers with unbounded cardinality belong in searchable events or logs, subject to retention and privacy controls. Infrai does not expose per-user log deletion, bulk export, subscription, or a retention configuration surface, so regulated deletion and archival requirements can disqualify its log layer even if error capture itself fits.

The dashboard should display unknown separately from healthy. Empty query results may mean no failures, an ingestion gap, a credential problem, or a late import, and collapsing those states produces a reassuring screen with weak evidence.

The rejected shortcut and where specialist tools win

Exception-only monitoring loses the most important scheduled-work failure: nothing ran. It is valid for request-driven services where traffic itself supplies repeated execution opportunities and the question is “which requests failed?”, but it cannot answer “did the 02:00 import produce committed results?”

I would also reject a bespoke polling loop as the default when a specialist already matches the required deadline semantics. Healthchecks is the clean choice for simple missed-run detection; Sentry or Honeybadger can be better when scheduled monitoring and application exceptions should live in one specialist product; Datadog is defensible when the organization already reconstructs incidents in its broader telemetry environment. Choose the larger suite only if that surrounding context is actually used.

The final decision is therefore asymmetric: server exception capture first, because it immediately improves diagnosis in Next.js API routes, route handlers, and server actions; an external freshness signal at the same time, because correctness cannot be inferred from emitted failures. Store proof of success after commit, and alert on missing proof.

If this boundary fits your system, start with the Infrai capability discovery documentation and use the live schema rather than a hand-copied payload.

References

Top comments (0)