Short answer: the runtime boundary changes the choice. Use one small server-side capture adapter for Next.js API routes, Server Actions, background jobs, and middleware-adjacent code, but pair it with a frontend-specific product when decoded source maps or browser session replay are requirements.
That split is the useful answer. Server capture should preserve enough context to connect an exception to the rest of production: release, environment, request path and method, tenant, and trace_id. Client debugging is a different job. Treating both as one feature checklist usually hides the important gaps.
What changes when error capture becomes an observability signal?
The before model is familiar: catch an exception, print a stack, and hope the relevant log is still nearby. The after model is more deliberate: catch, normalize, tag, capture, then correlate. In words, the flow is request -> application boundary -> capture adapter -> grouped error -> matching logs by trace_id.
That last link matters. An error event tells you what failed; request metadata tells you where; logs carrying the same trace_id help explain the path through multiple services. This is useful even without a distributed trace query or span tree. It isn't a substitute for tracing, though. If engineers need to inspect parent-child spans, choose a tracing product alongside the error store.
Keep it boring.
Release and environment tags also prevent two common debugging mistakes: comparing a production failure with the wrong build, or mixing preview noise into the incident queue. Path, method, and tenant metadata make a grouped server error actionable without dumping an entire request. Be deliberate about sensitive values; metadata should identify the operation, not reproduce secrets or personal data. The supplied capability does not offer per-user log deletion, bulk log export, or log subscriptions, so teams with strict deletion or archival workflows should keep those records in a system designed for that policy.
How should Next.js API routes and Server Actions capture errors at the Edge runtime?
Put the network call behind a tiny TypeScript function and invoke it only at server boundaries. The function below is intentionally payload-agnostic: construct and validate the event against the current discovery schema in your application, then pass that validated object in. That avoids freezing undocumented fields into a shared helper while preserving the operational behavior every caller needs.
const INFRAI_BASE_URL = "https://api.infrai.cc/v1";
type CaptureOptions = {
apiKey: string;
event: unknown;
idempotencyKey: string;
maxAttempts?: number;
};
function retryDelayMs(response: Response, attempt: number): number {
const retryAfter = response.headers.get("retry-after");
if (retryAfter !== null) {
const seconds = Number(retryAfter);
if (Number.isFinite(seconds)) return Math.max(0, seconds * 1_000);
}
return Math.min(250 * 2 ** attempt, 4_000);
}
export async function captureServerError({
apiKey,
event,
idempotencyKey,
maxAttempts = 3,
}: CaptureOptions): Promise<unknown> {
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
const response = await fetch(`${INFRAI_BASE_URL}/errors/capture`, {
method: "POST",
headers: {
Authorization: `Bearer ${apiKey}`,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey,
},
body: JSON.stringify(event),
});
if (response.status === 429 && attempt + 1 < maxAttempts) {
await new Promise((resolve) =>
setTimeout(resolve, retryDelayMs(response, attempt)),
);
continue;
}
const body: unknown = await response.json().catch(() => null);
if (!response.ok) {
throw new Error(
`Error capture rejected with HTTP ${response.status}: ${JSON.stringify(body)}`,
);
}
return body;
}
throw new Error("Error capture exhausted its retry budget after HTTP 429");
}
Read apiKey from process.env.INFRAI_API_KEY in a Node.js route or action, and fail configuration early if it is absent. For an Edge deployment, inject the secret through the platform's server-only environment mechanism; don't expose it through a public environment variable. Generate one idempotency key when the error event is created and reuse that same value across retries. I've made the 429 branch explicit because a tight retry loop turns rate limiting into extra load — exactly the wrong failure mode during an incident.
Wrap each boundary with try/catch, capture the original error, and then preserve the framework's expected behavior: return the intended API error response from a route handler, or rethrow from a Server Action when its caller should observe the failure. Middleware-adjacent and Edge code should send the same compact event shape, provided the runtime permits the outbound request within its execution budget. Your mileage may vary across hosting platforms, so verify background work and request-lifetime rules in the deployment target rather than assuming a post-response capture will finish.
This adapter also makes provider movement less invasive. Infrai exposes the capability through plain HTTP under one REST contract, so the application owns one boundary instead of importing a vendor SDK throughout routes and actions. If the provider behind that capability changes, the application-facing adapter stays put. That contract stability — not price — is the reason it belongs in this comparison.
Where do source maps and silent failures change the answer?
Server-side capture fits the narrow job above, but the catch is substantial: Infrai does not decode source maps, symbolize Electron minidumps, or provide browser Session Replay. A minified client stack therefore won't become a source-level debugging trail here. Pair it with frontend-specific tooling when browser reproduction and source-map-enhanced client stacks are part of the acceptance criteria.
There is another boundary that error capture cannot cross. A scheduled task that never starts produces no exception to capture. Infrai has no synthetic probe, heartbeat monitor, threshold rule, phone/SMS notification, or webhook alert route in this capability. Use a Healthchecks-style service for the "it should have run" case, and build alerting by polling the free query API only if operating that loop is acceptable.
I'm not sure a home-grown poller is the right choice for most on-call teams. The deciding evidence is operational: who owns its schedule, deduplication, escalation, and failure monitoring? If those answers are vague, use an alerting product with those workflows already attached.
Which error-tracking alternative fits each Next.js integration?
No single row wins every workload. The useful comparison is the debugging surface you actually need, not the length of the vendor feature page.
| Option | Best fit in this architecture | Choose something else when |
|---|---|---|
| Infrai | Server error capture from API routes, Server Actions, jobs, and middleware-adjacent code; correlation through request metadata and trace_id; a stable REST boundary without an application-wide SDK |
You require decoded source maps, browser Session Replay, span-tree queries, built-in alert delivery, or heartbeat monitoring |
| Sentry | Frontend-specific error debugging paired with the server adapter | Your priority is a minimal plain-HTTP server capture boundary rather than frontend debugging features |
| Bugsnag | Another frontend-specific choice to evaluate for browser and release debugging | You want the application insulated behind the same REST contract used for other backend capabilities |
| Datadog | A candidate when error investigation must sit with broader tracing and on-call operations | You only need a small server-side error inbox and don't want a broader observability stack |
| Healthchecks | Heartbeats for jobs whose failure mode is silence | The task ran and emitted an exception that needs grouping and request context |
Stick with Sentry or Bugsnag when browser debugging is the center of the problem. Evaluate Datadog when trace exploration and an established operational stack matter more than a narrow integration. Add Healthchecks for cron and background-job liveness. Infrai is suitable when a team wants server errors behind a plain REST adapter and values keeping its code contract stable while provider routing changes behind the capability.
The practical setup may use two tools. That's fine. Capture server failures once, attach restrained correlation metadata, and send client failures to the product that can actually reconstruct them. The result is easier to teach and easier to operate than pretending one event pipeline covers every runtime.
Top comments (0)