Short answer: capture one structured failure envelope from every Next.js API route, server action, and edge execution path, join it to a stable experiment assignment and request correlation ID, and treat source maps as restricted reconstruction data rather than as the error-tracking system itself. For a marketplace comparing an experiment across tenant cohorts, this is the least complex design that can answer the question that matters after an incident: did the treatment change the kind, location, or tenant distribution of server errors?
The integration example below starts with evidence governance, not an SDK. SDK choice can wait. If the event contract cannot survive a runtime boundary or explain a cohort delta, adding a dashboard only makes an incomplete story easier to look at. The design sequence is custody first, capture second, reconstruction drill third; tooling enters only after those constraints are testable.
How can Next.js API routes and server actions govern edge runtime errors?
Use the same logical envelope at every capture point, but keep the transport adapter local to each runtime. The envelope needs an event ID, UTC timestamp, deployment identifier, runtime, operation, outcome, normalized error class, correlation ID, tenant cohort, experiment assignment, and a source-map release key. Capture the original exception at the closest boundary that can add operation context, then send the normalized envelope to a collector outside the request's decision logic. Don't make successful business work depend on the telemetry destination accepting the event.
A route handler can name an HTTP operation. A server action can name the business command it attempted. An edge path can identify its runtime and deployment without pretending it has the same execution environment as a long-lived server process. Those labels differ, but the fields used for incident reconstruction must not. This is the central constraint — cross-cohort comparison fails when one surface calls a field variant, another calls it experiment, and a third omits assignment on errors.
Keep it boring.
The capture boundary should distinguish an expected rejected operation from an unexpected exception. RFC 5424 defines eight severity levels, from Emergency through Debug, and explicitly warns that the meaning of a severity is locally defined. Severity is useful only after the team writes down its own mapping. A marketplace might classify invalid buyer input as a recorded business rejection, while a violated invariant becomes an error event; copying every non-success into the same severity bucket destroys that distinction.
Here is a Python representation of the contract and its validation. It isn't a Next.js wrapper; it is the language-neutral ingestion model that every runtime adapter must produce, shown in Python because the receiving data layer should validate independently of the emitting application.
from dataclasses import dataclass
from datetime import datetime, timezone
from enum import Enum
from typing import Literal
from uuid import UUID
class Outcome(str, Enum):
SUCCEEDED = "succeeded"
REJECTED = "rejected"
FAILED = "failed"
@dataclass(frozen=True)
class FailureEnvelope:
event_id: UUID
occurred_at: datetime
deployment_id: str
runtime: Literal["node", "edge"]
operation: str
outcome: Outcome
error_class: str | None
correlation_id: str
tenant_cohort: str
experiment_assignment: str
source_map_release: str | None
def validate(self) -> None:
if self.occurred_at.tzinfo != timezone.utc:
raise ValueError("occurred_at must be UTC")
if self.outcome is Outcome.FAILED and not self.error_class:
raise ValueError("failed events require error_class")
if not self.deployment_id or not self.correlation_id:
raise ValueError("deployment_id and correlation_id are required")
The collector should reject a malformed envelope with a stable client-error response and a machine-readable reason, while the application records that telemetry delivery failed without replacing the original application result. Exact response codes and retry policy belong in the collector contract; inventing them here would be false precision. The important separation is between the marketplace operation and the observation of that operation.
Immutable cohort records define the storage contract
An error tracker is an index over evidence. It isn't the evidence boundary. For cohort analysis, retain the normalized event in append-only object storage or another immutable log before deriving aggregates, because the questions asked during an incident change as the investigation proceeds. The first query may compare failure counts between control and treatment. The next may isolate one deployment, one operation, and a narrow time window. A pre-aggregated counter cannot recover fields that were discarded at ingestion.
This does not justify storing everything. Request bodies, authorization headers, session tokens, free-form tenant names, and raw exception messages can carry secrets or personal data; they also make poor grouping keys. Define an allowlist, replace direct tenant identity with a cohort label or a controlled pseudonymous key, and cap the size of every free-text field. Preserve enough context to reproduce the decision path, not enough to recreate a user's account.
The assignment deserves special care. Record the experiment identifier and assigned branch as they were known when the operation ran. Do not infer assignment later from the tenant's current configuration, because rollouts move and cohort membership can change. Also retain a deployment identifier and the release key that selects the matching source map. Without both, two minified frames from different builds can look identical while referring to different source code.
This is where storage architecture earns its keep: write raw envelopes partitioned by coarse time and deployment, maintain a separately governed source-map artifact store, and build queryable summaries from those records. Raw evidence should have a documented retention period. Derived metrics can live longer if their labels cannot identify a tenant. I'm not sure there is one defensible retention period for every marketplace; legal obligations, incident response time, experiment duration, and storage budget resolve that decision, not a generic observability checklist.
Prometheus's instrumentation guidance supports the same separation from the metrics side. It recommends labels for dimensions such as response code and method, but cautions against high-cardinality labels and advises investigating alternatives when cardinality exceeds roughly 100 or can grow without bound. A correlation ID, stack trace, tenant ID, or raw error message therefore belongs in event storage, not in metric labels. Cohort and bounded experiment branch may be valid labels only when their possible values are deliberately constrained.
No metric can reverse that loss.
Source-map custody belongs to data governance
Source maps answer a narrow reconstruction question: which authored source location corresponds to a transformed stack location for this exact release? They do not supply the missing cohort, correlation, operation, or deployment context. Treating source maps as the integration is a category error. The useful integration links an error event to a release-specific artifact and performs symbolication in a controlled processing path.
The operational risk is accidental disclosure. Authored source and embedded source content may expose implementation details, so keep production source maps out of public asset delivery unless public access is an explicit decision. Store them with deployment artifacts, authorize the symbolication worker to read them, log access, and delete them according to the same release-retention policy used by incident responders. A release key must be immutable: overwriting current turns old evidence into a stack trace decoded with the wrong build.
Edge runtime limitations change the adapter, not the evidence contract. Assume fewer environment capabilities until the deployed runtime proves otherwise; avoid relying on process-global buffering, filesystem access, or a runtime-specific exception object in the shared design. The adapter should serialize the allowlisted fields promptly and hand them to a bounded transport. If a runtime cannot support the preferred transport or local symbolication, emit the envelope to a collector and perform enrichment there. That collector is the practical alternative to forcing every API route, server action, and edge path through one environment-specific error tracking integration.
There is a catch: asynchronous delivery can lose the last event when an execution context ends, while synchronous delivery adds latency and couples the user request to the collector. The right choice depends on the runtime's documented completion model and the business consequence of missing one event. For checkout or settlement operations, a durable application outbox may justify the extra write. For a read-only recommendation experiment, a bounded best-effort send plus aggregate counters may be enough. Your mileage may vary, but the decision should be explicit and tested under termination, timeout, and network refusal rather than assumed from local development.
Measure both paths.
Reconstruction drills expose the useful trade-offs
Run the comparison as an incident drill, not as a feature checklist. Start with a treatment-cohort alert, ask an operator to recover the deployment, operation, relevant events, and authored stack location, then record where the evidence chain stops. Each design can be implemented with self-hosted or managed components, and none is automatically correct for every marketplace.
Repeat the drill after deployment.
| Design | What survives an incident | Main limitation | Use it when |
|---|---|---|---|
| Metrics only | Bounded rates and cohort deltas | Cannot recover a single request path or stack | Aggregate regression detection is sufficient |
| Error events plus source maps | Exception context and authored locations for a release | Weak causal history unless correlation and assignment are present | Failures are exception-driven and request reconstruction is enough |
| Structured events plus traces | Cross-service timing and causal links | Sampling and storage policy can remove rare evidence | The request crosses services and dependency order matters |
| Durable envelope plus derived metrics | Re-queryable evidence and cheap bounded alerts | More schema governance, retention work, and delayed enrichment | Experiment comparison and post-incident reclassification are required |
For this marketplace job, the last option is the default because tenant-cohort comparison is an analytical requirement, not a presentation preference. It is not suitable when the data cannot be retained under the marketplace's privacy policy, when the team cannot operate schema migrations, or when a low-risk feature needs only a bounded failure-rate alert. Stick with metrics only for that narrower case. Choose traces when the unanswered question is service order or latency rather than experiment assignment.
Cost follows cardinality and retention more reliably than it follows vendor branding. Estimate event volume from requests multiplied by capture rate, measure compressed bytes per envelope, set raw and derived retention independently, and put a hard budget on label combinations. Prometheus specifically warns against labels with unbounded cardinality; ignoring that limit creates an operational problem even when ingestion looks inexpensive. Sampling can control volume, but head sampling may erase a rare cohort-specific failure. A defensible policy keeps all failed envelopes for critical operations, samples successful context, and records the sampling decision so analysts don't mistake the retained set for the population.
Alerting should use bounded metrics derived from the same validated stream: failure count, attempt count, deployment, operation, cohort, and experiment branch. Don't label a metric with event_id or correlation_id. During an incident, the alert supplies the coarse slice; the event store supplies the exact records; the release key supplies the matching source map. That chain is short enough to test and strict enough to audit.
Migrate one operation and verify the evidence chain
Begin with one non-critical marketplace operation. Publish the envelope schema, its privacy allowlist, severity mapping, maximum field sizes, and retention policy. Add contract tests that feed equivalent failures from API routes, server actions, and edge adapters into the collector, then assert that the stored records have the same deployment, operation, cohort, assignment, and correlation semantics. Include termination and collector-refusal tests, because a happy-path capture demo says little about incident evidence.
Next, dual-write the new envelope beside the current error tracking path for one deployment window, without changing alerts. Compare counts by bounded dimensions and inspect a small authorized sample for symbolication against the correct release. Once the new stream accounts for the expected operations and the privacy review is complete, derive alerts from it, freeze the old schema, and retire the old path according to its retention obligations. Roll back by switching alert reads to the previous stream; do not delete evidence during the decision window.
The acceptance test is concrete: given an alert for a treatment-cohort increase, an operator can find the affected deployment and operations, retrieve the corresponding envelopes without direct tenant identity, decode stack locations with the correct source map, and state what evidence was sampled or omitted. If that chain breaks, the integration isn't finished.
References
Further reading for bounded metric dimensions and severity semantics:
- Prometheus, "Instrumentation best practices": https://prometheus.io/docs/practices/instrumentation/
- IETF RFC 5424, "The Syslog Protocol": https://datatracker.ietf.org/doc/html/rfc5424
Top comments (1)
The approach you've outlined for structured failure envelopes is both methodical and insightful, especially the emphasis on capturing errors at the closest boundary to add useful context. I appreciate how you've prioritized evidence governance before SDK implementation; it’s a crucial consideration for maintaining clarity in data integrity. It might be worth exploring how automated validation tools can aid in ensuring that the envelopes conform to the expected structure, potentially reducing manual oversight. If you’re considering expanding the tooling aspect of this project, I’d be interested in discussing a potential collaboration to help enhance this area. What challenges have you encountered in maintaining consistency across different runtime environments?