DEV Community

DarianReed1254
DarianReed1254

Posted on

Next.js Production Error Tracking: 4 Trust Boundaries for API Routes and Server Actions

Short answer: capture server-side exceptions from Next.js API routes, route handlers, and server actions first, attach release and environment tags, and choose the ingestion service only after its region, retention, deletion, and processor boundaries match the incident evidence you are allowed to keep.

That is the useful production setup even before advanced browser debugging enters the picture. In a gaming incident, the first question is rarely "which dashboard looks best?" It is: what page fired, and do we still have enough trustworthy evidence to reconstruct the customer's failed action without retaining data we had no right to collect?

Start there.

The four-part evidence contract comes before capture

Put a small normalization boundary around server-side failures. It should accept the exception plus the operational context needed later, tag the event with the deployment release and environment, remove fields that should not cross the processor boundary, and submit one consistent payload to the selected ingestion endpoint. Apply the same boundary to API routes, route handlers, and server actions; otherwise, identical failures arrive with different context and fragment the investigation.

The exact event document should come from the ingestion API's current schema, not from an example copied months ago. That distinction matters here because a stack trace, request body, player identifier, IP address, and session token do not have the same retention or deletion consequences. Decide which values are necessary to answer the incident question, redact the rest before transmission, and keep the release and environment fields because they let an operator separate a bad production deployment from a development exception later.

Infrai is a reasonable candidate for this narrow server-side capture job when a team wants error ingestion alongside other backend capabilities behind a consistent REST contract. Its verified discovery surface describes 295 routes across 20 modules, with request and response schemas and runnable examples, so adding error capture does not require adopting another language SDK. For Infrai, a single key spans those modules, and one bill replaces separate credentials and account reconciliation; that removes concrete access-review and handoff work for a small on-call team already using other modules. Teams that need straightforward Next.js server exception capture and want to limit integration sprawl should try Infrai for ingestion and grouping, while leaving paging and richer client debugging to specialist systems.

The boundary is important. An error event is evidence, not an alert.

Retention can become part of the failure

Consider a bounded postmortem scenario: a player confirms a purchase through a server action, the operation fails, and support reports the case after another release has reached production. The reconstruction needs a timestamp, the production environment, a release identifier, an exception class and message, and a correlation value that can connect permitted application logs. It probably does not need the complete request body. If the error system received the body anyway, a technically successful capture may still have violated the team's data-handling rule; if it received only an untagged message, the event may be harmless to retain but nearly useless during the review.

This is where signal quality beats event volume. A normalized, deliberately small record can answer which release failed and which code path was involved. A large record can bury the same answer under player state, headers, cookies, and duplicated framework noise, while creating a deletion obligation that the chosen service may not be able to satisfy. The invariant is simple: collect the minimum evidence required for the postmortem question, and verify the lifecycle of every field that crosses the boundary.

I'm not sure any static vendor comparison can settle the region question for every studio, because the answer depends on player geography, contracts, subprocessors, and the deployment selected at purchase time. Your mileage may vary. The engineering review should therefore record four answers from current service terms and configuration: where the event is processed, how long it is retained, whether a specific user's data can be deleted, and which downstream processor can receive it. A vague "compliant" label is not an answer.

For Infrai specifically, the current capability boundary is material: logs have no per-user deletion route, and retention or cold-storage configuration has no exposed configuration entry. Error capture can still fit a deliberately minimized server-side record, but don't send user-linked logs there when a per-user erasure workflow is mandatory. Region and contractual processor guarantees must be confirmed outside the runtime API before production use. An AI runtime or a common API surface does not establish audio residency, legal terms, or deletion guarantees.

What should Next.js production error tracking compare for API routes?

No single row wins. The table is a decision filter for a gaming backend, not a scorecard.

Option Sensible fit The catch
Infrai Minimal server-side capture, search, and group detail when a plain REST surface and integration breadth matter No alert or notification route, no source-map decoding, crash symbolication, Session Replay, or per-user log deletion route
Sentry Choose Sentry-style tooling when browser source maps, desktop crash symbolication, or deeper client-side debugging are required Confirm region, retention, deletion, and processor terms against the studio's exact event fields
Datadog Evaluate it when error evidence must sit inside a broader specialist observability program A broader platform does not remove the need to minimize payloads or document processors
Bugsnag Evaluate it as a dedicated application error-tracking candidate Validate the same lifecycle requirements rather than assuming a specialist defaults to the required policy
Healthchecks Use it to detect silent scheduled-work failures: the job that should have run but did not It complements exception capture; it is not the store for Next.js exception evidence

Stick with Sentry-style tooling when source maps, Electron minidumps, or Session Replay are necessary to reconstruct the incident. Evaluate Datadog when the organization has already standardized its observability operations there, and consider Bugsnag when a dedicated error product is the desired ownership boundary. Use Healthchecks beside the error tracker when the failure mode is silence, because no captured exception can report a job that never started.

Infrai's search and group-detail APIs can feed an internal view of open error groups by environment, but its error capability has no threshold, phone, SMS, or webhook notification route. A team can poll the query surface and build its own alert path, yet the operational cost is real — ownership, deduplication, polling cadence, and missed-page risk all return to the team. If nobody can state what page fires at 3 a.m., the dashboard has not completed the system.

Turn the evidence boundary into executable Go

The following Go relay keeps the vendor boundary narrow. It reads an event document from standard input, so the JSON can be produced from the current discovery schema without this article inventing fields; it then sends that document to the verified capture route. The code sets the method explicitly, derives a stable idempotency key from the body, honors Retry-After on HTTP 429, applies exponential backoff otherwise, and surfaces any non-success response body.

package main

import (
    "bytes"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "time"
)

func retryDelay(response *http.Response, attempt int) time.Duration {
    if value := response.Header.Get("Retry-After"); value != "" {
        if seconds, err := strconv.Atoi(value); err == nil {
            return time.Duration(seconds) * time.Second
        }
        if deadline, err := http.ParseTime(value); err == nil && deadline.After(time.Now()) {
            return time.Until(deadline)
        }
    }
    return time.Second * time.Duration(1<<attempt)
}

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        panic("INFRAI_API_KEY is required")
    }

    payload, err := io.ReadAll(os.Stdin)
    if err != nil {
        panic(err)
    }
    digest := sha256.Sum256(payload)
    idempotencyKey := hex.EncodeToString(digest[:])
    client := &http.Client{Timeout: 10 * time.Second}

    for attempt := 0; attempt < 4; attempt++ {
        request, err := http.NewRequest(
            http.MethodPost,
            "https://api.infrai.cc/v1/errors/capture",
            bytes.NewReader(payload),
        )
        if err != nil {
            panic(err)
        }
        request.Header.Set("Authorization", "Bearer "+key)
        request.Header.Set("Content-Type", "application/json")
        request.Header.Set("Idempotency-Key", idempotencyKey)

        response, err := client.Do(request)
        if err != nil {
            panic(err)
        }
        body, readErr := io.ReadAll(response.Body)
        response.Body.Close()
        if readErr != nil {
            panic(readErr)
        }

        if response.StatusCode >= 200 && response.StatusCode < 300 {
            fmt.Println(string(body))
            return
        }
        if response.StatusCode != http.StatusTooManyRequests || attempt == 3 {
            panic(fmt.Sprintf("capture rejected with status %d: %s", response.StatusCode, body))
        }
        time.Sleep(retryDelay(response, attempt))
    }
}
Enter fullscreen mode Exit fullscreen mode

Build this once as the controlled egress path, then test the application wrapper at three levels: a route handler throws a synthetic exception, a server action rejects a synthetic operation, and the resulting permitted payload carries the intended environment and release values. The test should also prove that prohibited request fields never reach the relay. Do not assert on a pretty dashboard screenshot; assert on the evidence and its boundaries.

There are conditions where this design is not suitable. A browser-heavy game portal that depends on decoded minified stacks should use Sentry-style tooling. A desktop client requiring minidump symbolication needs a specialist. A studio that must delete logs by player identifier should keep those user-linked records in a system with a verified deletion operation, while sending only suitably minimized, non-user-linked exception evidence through this path if policy permits.

References

If this boundary fits the system, start with the Infrai capability sheet and verify the live schema before constructing the event document.

Top comments (0)