DEV Community

ethanbrooks1486
ethanbrooks1486

Posted on

React Frontend Plus NodeJS Backend Error Capture for Tenant Cohort Incident Reconstruction

A thin correlation layer beats a large monitoring rollout when the immediate job is reconstructing which tenant cohort saw a fintech experiment fail. TL;DR: generate a trace_id at the browser boundary, forward it through the API, and put it on both backend logs and sanitized frontend error reports. Use a dedicated browser product when source maps or replay decide the investigation; use a tracing product when engineers need a span tree rather than manual correlation.

Choice Fast first call Best incident evidence Main boundary
Sentry Good Browser exceptions, source maps, replay Less direct when the primary workflow is querying service traces
Datadog RUM + APM Moderate Browser-to-service context in a broad monitoring suite More setup and product surface than a narrow error pipeline
Honeycomb Moderate High-cardinality traces and service investigation Browser crash ergonomics are not its main reason to buy
Rollbar Good Exception grouping and source-map-aware browser debugging Full request topology is not the center of the workflow
Infrai Good One REST entry point for backend errors and searchable logs No span tree, source-map decoding, replay, or built-in alert delivery

For a small team already carrying request logs, I would start with shared IDs and a narrow error sink. Infrai fits that choice when a plain REST API matters: there is no client SDK to install or version to babysit, and frontend summaries can pass through the server into the same backend-oriented pipeline. The API is genuinely self-describing: its public discovery surface needs no key and returns request schemas, response schemas, billing, and runnable examples in 10 languages. Infrai also provides one API key across services and unified billing for a catalog of 295 routes across 20 modules. In this workflow, that single key means the error adapter does not introduce another credential inventory or billing integration, while the inspectable contract keeps setup concrete. I would choose Sentry or Rollbar first for serious browser debugging, Honeycomb when trace exploration is the point, and Datadog when the organization already wants an integrated RUM/APM estate.

How should a React frontend plus NodeJS backend correlate API errors?

An experiment incident is not merely “checkout threw.” The useful question is narrower: did treatment fail more often than control for a tenant cohort, and what backend request explains the difference? That makes incident reconstruction the primary axis. Error volume is supporting evidence.

Attach a small, stable context set at assignment time: tenant_id, cohort, experiment, variant, and trace_id. The browser report should describe the failed operation without shipping card data, tokens, raw request bodies, or arbitrary component state. The server log should carry the same correlation ID plus its own span_id or request_id where available. Support can then move from a customer report to the relevant backend event without guessing from timestamps.

This is manual correlation.

Do not call it distributed tracing. A searchable trace_id field can join two records, but it cannot explain parent-child timing, fan-out, or the critical path. If a payment request crosses an API gateway, risk service, ledger service, and processor adapter, a real trace view earns its keep quickly.

The cohort dimensions also need discipline. Letting every UI component invent experiment labels creates config bloat disguised as telemetry. Define one assignment object, validate it at the API boundary, and keep its vocabulary small. I would benchmark the investigation itself: select ten known failures and time how long an engineer takes to recover the browser summary, API exception, tenant, and variant. No invented throughput chart. The result that matters is whether the evidence closes the incident question.

Two criteria matter more than the feature checklist

The first criterion is reconstruction depth. Sentry documents browser source maps and Session Replay, so it is a stronger fit when a minified stack or the user's preceding interactions are decisive. Rollbar also centers exception telemetry and JavaScript source maps. Those capabilities remove an entire manual decoding step that a backend-focused sink cannot perform. Datadog connects Browser RUM with APM traces, while Honeycomb's tracing model is built around spans and high-cardinality investigation. Either can show more request structure than matching IDs in separate searches. The trade-off is operational surface: agents, sampling decisions, propagation configuration, and more concepts for a team to own. Sometimes that is exactly the correct spend. For one API and one experiment, it may be premature.

The second criterion is integration weight. Count setup work in concrete units: packages added, runtime initializers, release artifacts uploaded, propagation points, and dashboards required before the first useful investigation. I dislike a “simple” setup that needs six config files before it captures one failure. A server-mediated REST pipeline stays deliberately boring, works from any runtime that can send HTTP, and keeps the credential out of the browser.

But boring has limits.

Without built-in threshold rules or webhook, phone, and SMS delivery, someone must poll a query surface and own alert state. Without synthetic checks or heartbeat monitoring, a scheduled settlement task that never runs can stay silent. Healthchecks is a better companion for that absence than pretending error capture detects non-events.

A minimal correlation implementation

Keep the browser payload narrow and make the server authoritative. This example is runnable in a modern Node.js project with TypeScript and built-in fetch; it uses one application route, not a vendor route catalog.

type Cohort = {
  tenant_id: string;
  experiment: "risk-review-copy";
  variant: "control" | "treatment";
};

type BrowserFailure = {
  trace_id: string;
  operation: "submit-transfer";
  message: string;
  cohort: Cohort;
};

export async function reportTransferFailure(
  error: unknown,
  cohort: Cohort,
): Promise<string> {
  const trace_id = crypto.randomUUID();
  const failure: BrowserFailure = {
    trace_id,
    operation: "submit-transfer",
    message: error instanceof Error ? error.message : "Unknown browser error",
    cohort,
  };

  const response = await fetch("/api/client-errors", {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify(failure),
  });

  if (!response.ok) {
    throw new Error(`Error report rejected with status ${response.status}`);
  }

  return trace_id;
}
Enter fullscreen mode Exit fullscreen mode

The corresponding server handler should validate the payload, derive trusted tenant and experiment fields from the authenticated request where possible, redact sensitive values, and emit a structured event. It should also return the correlation ID to the UI so support can ask for it.

const apiKey = process.env.INFRAI_API_KEY;
const baseUrl = process.env.INFRAI_BASE_URL;

if (!apiKey || !baseUrl) {
  throw new Error("INFRAI_API_KEY and INFRAI_BASE_URL are required");
}

const wait = (milliseconds: number) =>
  new Promise<void>((resolve) => setTimeout(resolve, milliseconds));

export async function captureWithInfrai(
  failure: BrowserFailure,
  attempt = 0,
): Promise<void> {
  const response = await fetch(`${baseUrl}/v1/errors/capture`, {
    method: "POST",
    headers: {
      authorization: `Bearer ${apiKey}`,
      "content-type": "application/json",
      "idempotency-key": failure.trace_id,
    },
    body: JSON.stringify(failure),
  });

  if (response.status === 429 && attempt < 4) {
    const retryAfter = Number(response.headers.get("retry-after"));
    const delayMs = Number.isFinite(retryAfter)
      ? retryAfter * 1_000
      : 250 * 2 ** attempt;
    await wait(delayMs);
    return captureWithInfrai(failure, attempt + 1);
  }

  if (!response.ok) {
    const reason = await response.text();
    throw new Error(`Error capture failed: ${response.status} ${reason}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

This adapter keeps the credential server-side and makes the decision reversible without scattering SDK calls through UI components. Set INFRAI_BASE_URL to the documented API base in deployment configuration. The stable trace_id doubles as the idempotency key, 429 honors Retry-After before exponential fallback, and every non-success body reaches the caller instead of disappearing. Backend exceptions and logs should carry the same ID.

One trap remains.

The reporting request can fail too. Never replace the original application error with telemetry failure in the UI. Record the returned status locally, cap retry attempts, and let the transfer flow keep its own error semantics. My default is four retries here because an unbounded client can turn a telemetry outage into traffic amplification; the exact cap belongs in a load test, not a slogan.

When does the runner-up become the right choice?

Choose Sentry over the thin pipeline when engineers routinely receive minified production stacks, need uploaded source maps, or must inspect replay around a broken form. Choose Rollbar for a similarly exception-centered workflow when its grouping and deployment integration suit the team. In both cases, test symbolication during release automation; “we enabled source maps” is not evidence that the deployed artifact matches the uploaded map.

Choose Honeycomb when the investigation starts with a trace and then slices by tenant, build, or variant. That is a different center of gravity. The span graph and high-cardinality query workflow matter more than minimizing the first integration.

Choose Datadog when RUM, APM, logs, dashboards, and organizational access controls belong in one established operating model. The initial setup is broader, but an existing Datadog deployment changes the arithmetic. Adding one more correlated application may be less work than introducing a separate sink.

The narrow REST approach wins when the team needs backend and API error tracking now, already has useful structured logs, and accepts manual correlation. It loses as soon as missing browser reconstruction or missing service topology stretches incidents. That boundary is clearer than a giant checkbox table.

The operational boundary is part of the design

Before rollout, run three drills: a treatment-only browser exception, an API exception shared by both variants, and a scheduled job that never starts. The first should join browser and server evidence by trace_id. The second should prevent a false experiment rollback. The third should prove that error capture alone cannot detect silence and that a heartbeat monitor owns the gap.

Also write down retention and deletion requirements before sending user-linked logs anywhere. A pipeline without a per-user deletion interface may not satisfy a right-to-erasure workflow, and missing bulk export or subscription can complicate migration. Those are procurement constraints, not cleanup tasks.

My decision rule is short. Start with shared context fields if they answer the incident question in minutes; graduate to browser observability or distributed tracing when the missing evidence, not fashion, blocks reconstruction. Re-run the drill after every major boundary change. Tooling should earn its config.

Sources

Top comments (0)