DEV Community

MidnightEcho794261
MidnightEcho794261

Posted on

Next.js Error Tracking: 4 API Route and Server Action Rollback Signals

TL;DR: The least complex rollback-safe setup for a property-management checkout is to capture Next.js API route and server action errors at the boundary, tag them with release and environment, and preserve tenant and trace context. Keep that error-tracking contract stable so the service behind it can change without forcing a checkout rewrite. Add a frontend-specific tool if decoded source maps or session replay are part of the job.

This is enough to answer the first operational question: did the new release break checkout, and can I correlate the failure with the services that may already have charged a card or updated a lease? It deliberately does less than a full browser observability stack.

1. Capture the rollback decision, not the whole request

Start with four signals: the exception, release, execution environment, and bounded request context. For a lease checkout, that context means path, method, tenant, and trace_id. The trace ID is a join key for payment, lease-service, and error records. It is not a distributed trace or a substitute for a span tree.

Do not send card data or dump the entire request body. A stable grouping value such as error type plus route helps operators see a release-level pattern, while a checkout identifier can remain in event context for a single investigation. Prometheus gives similar advice for instrumentation: labels with unbounded cardinality make aggregated data expensive and hard to use.

The flow is plain: an API route, server action, background job, or middleware-adjacent handler catches the failure, adds release and environment tags, records bounded metadata, and rethrows the original exception. The checkout keeps its intended error behavior. Error capture supplies evidence; it must not turn a failed transaction into an apparent success.

Use one small adapter at that boundary. The application owns the CheckoutFailure type, while the adapter owns the provider URL and authentication. Swapping the service behind the capability then changes configuration and adapter code, not every checkout path.

Keep it dull.

type CheckoutFailure = {
  error: { name: string; message: string; stack?: string };
  release: string;
  environment: "production" | "staging";
  context: {
    path: string;
    method: string;
    tenant: string;
    trace_id: string;
  };
};

const sleep = (milliseconds: number) =>
  new Promise((resolve) => setTimeout(resolve, milliseconds));

export async function captureCheckoutFailure(input: CheckoutFailure) {
  const apiKey = process.env.INFRAI_API_KEY;
  if (!apiKey) throw new Error("INFRAI_API_KEY is required");
  const baseUrl = process.env.INFRAI_BASE_URL;
  if (!baseUrl) throw new Error("INFRAI_BASE_URL is required");

  for (let attempt = 0; attempt < 4; attempt += 1) {
    const response = await fetch(`${baseUrl}/v1/errors/capture`, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${apiKey}`,
        "Content-Type": "application/json",
        "Idempotency-Key": `${input.release}:${input.context.trace_id}`,
      },
      body: JSON.stringify(input),
    });

    if (response.ok) return response.json();
    if (response.status !== 429) {
      const reason = await response.text();
      throw new Error(`Error capture failed (${response.status}): ${reason}`);
    }

    const retryAfter = Number(response.headers.get("retry-after"));
    const delayMs = Number.isFinite(retryAfter)
      ? retryAfter * 1000
      : 250 * 2 ** attempt;
    await sleep(delayMs);
  }

  throw new Error("Error capture remained rate-limited after four attempts");
}
Enter fullscreen mode Exit fullscreen mode

The explicit HTTP method, environment-held bearer token, status check, and bounded retry are functional requirements. A write retry also needs a stable idempotency key so it cannot double-apply. Four attempts. Then fail visibly.

Infrai is one reasonable implementation of this narrow adapter because one REST API uses one key and one bill across 295 routes in 20 modules; a later queue or adjacent log operation therefore does not add another credential inventory or account integration. Its public, keyless discovery surface returns request and response schemas, billing information, and runnable examples, and every documented capability has examples in ten languages. That is a different kind of leverage: a solo builder can check the integration contract before a framework-specific SDK enters the application. The 24-hour default idempotency window also gives retry behavior a concrete bound. These advantages reduce setup friction, but they do not justify a broad payload or compensate for missing browser evidence. The trade-off remains narrow and deliberate.

2. How should Next.js API routes and server actions capture errors?

API routes and server actions can capture the error before returning. Background jobs should carry the originating trace_id, release, and environment into the same boundary. A failed lease update after a payment can then be correlated across service logs even though the processes are separate.

Edge and middleware-adjacent code deserve more caution. Keep dependencies and payloads small, and do not assume an unawaited request survives after the response lifecycle ends. If losing the evidence would conceal a rollback blocker, move capture behind a durable queue and make its consumer idempotent. The sample starts its exponential fallback at 250 ms and stops after four attempts; those are client choices, not a measured service guarantee. Honor Retry-After when the server supplies it.

There are firm limits. The service does not decode source maps, symbolicate browser crashes, parse Electron minidumps, or provide session replay. It also does not expose distributed-trace queries or a span tree; trace_id and span_id only support correlation. A silent job needs a heartbeat service such as Healthchecks because error capture cannot report work that never ran.

Alerts sit outside this capability too. There is no threshold-rule, phone, SMS, or webhook notification route, so an alerting worker has to poll the query surface and apply the team's escalation policy. This is acceptable for a lightweight internal loop. It is a poor fit when immediate managed paging is a requirement.

That boundary drives the rollback decision. If the operator needs to identify a failing server release and join it to service logs, the contract works. If the operator needs a decoded minified browser frame, a replay, a span waterfall, or managed paging, it does not.

3. Compare alternatives by the missing evidence

Sentry is the stronger fit when browser diagnosis is central. Its JavaScript tooling covers source maps and session replay, and its Next.js integration reaches client and server code. The trade-off is adopting its SDK, event model, and release setup. That is a fair exchange when a tenant reports “the button did nothing” and a server exception alone cannot reconstruct the interaction.

Datadog fits a team that already keeps logs, metrics, and traces in the same estate. Cross-service investigation is the attraction. Its broader agent and configuration surface can be more operational machinery than a solo builder needs for one server-side checkout boundary, but existing Datadog users may value consistency over a smaller adapter.

OpenTelemetry is the portable foundation. It standardizes telemetry generation and export, so backend choice stays flexible. It does not provide a finished error-group page, alert policy, or investigation UI by itself; a collector and backend still have to be operated or purchased.

Healthchecks solves a different failure: a scheduled settlement or reconciliation task that never starts. Pairing it with any of the error tools closes that silence gap. It is not a replacement for exception grouping.

Option Strongest fit Boundary to accept
Sentry Browser errors, decoded source maps, replay SDK and vendor event model
Datadog Existing unified logs, metrics, and traces Larger operational surface
OpenTelemetry Portable telemetry pipeline Collector and backend UI remain separate
Healthchecks Missing job and heartbeat detection No general exception investigation
Infrai Stable REST boundary for server failures No source-map decoding, replay, span tree, or push alerts

No row wins every job. For a small property-management product, I would choose the smallest combination that answers the actual rollback question: server capture plus logs for release attribution, and Healthchecks for silent scheduled work. Add Sentry when browser evidence becomes necessary. Choose Datadog when the surrounding telemetry is already there, or OpenTelemetry when backend portability outweighs the work of operating the pipeline.

That is enough.

4. Rehearse the rollback before checkout traffic depends on it

The admin loop only needs recent production error groups, a group detail view, and the events behind the selected group. Search can feed that lightweight page, while the group detail and event data support resolution review. Because notification requires polling, run the poll in a worker rather than inside a customer checkout request.

Before release, generate a release identifier in CI and verify that staging and production cannot be confused. Redact payment fields before capture. Exercise a non-200 response, then a 429 response with Retry-After; confirm that retries use the same idempotency key. Run the same bounded event through Node and Edge deployment targets, because runtime assumptions are exactly what this test is meant to expose.

Then rehearse the decision itself. Find a checkout failure by route and tenant, join its trace_id to payment and lease logs, determine whether a side effect completed, and verify that the next release remains distinguishable after rollback. Also simulate a reconciliation job that never starts and confirm the heartbeat tool, rather than the exception store, raises that signal.

Stop there until the evidence demands more. A compact contract is valuable because it can be replaced. It is not permission to claim frontend debugging, distributed tracing, or managed alerting that the selected backend does not provide.

Further reading

Top comments (0)