Skip to content
All posts

Classifying errors: client-side vs server-side

A TypeError thrown inside a React component and a TypeError thrown inside an Express handler are the same class of bug with very different operational consequences. Mixing them in one flat list makes triage slower and occasionally leads to the wrong person being paged. This post covers the origin-tagging we added — what signals it uses, where it shows up, and how to think about it during triage.

What "origin" means here

Origin is a binary tag: server or client. Server means the error fired inside a server-side runtime — Node, your Express or Fastify handlers, a worker, a background job. Client means the error fired inside a browser — a React render, a fetch call, a DOM event handler.

The distinction isn't about where the bug's root cause lives — that could be server code reached from a browser, or shared utility code running in both contexts. Origin specifically tags where the runtime failure was observed. That's what matters for triage.

Signal one: explicit SDK origin

If the SDK tags the event with an explicit origin or runtime field, we take it at face value. Synonyms like "server", "node", "backend" collapse to server; "client", "browser", "web" collapse to client. This is the preferred path, and we expect SDKs to emit this explicitly over time.

Signal two: browser metadata

When there's no explicit tag, we look at metadata.browser. The browser SDK fills this with a user-agent fragment; the server SDK fills it with an identity-packed string prefixed by "server:" or similar. If the value exists and doesn't start with the server-style prefix, the error is tagged client. If it starts with a server-style prefix, tagged server.

Signal three: file and URL shape

When metadata alone isn't enough, we look at the source file and URL. If the file path starts with http:// or https://, we treat the error as client — server-side runtimes don't throw with HTTP-scheme file paths. Same for source.url.

When none of the signals fire, we default to server. That's a conservative choice — a truly ambiguous error is more likely to be a backend emission than a browser one in most architectures.

Where the tag shows up

On the errors list page, every row renders an origin badge next to the severity badge. Server is blue, client is purple, each with a small icon. Click into the error to see the expanded detail view and the origin appears in the metadata chips alongside error type, URL, browser, OS, and timestamps.

The activity log records origin inside the metadata payload on every error_captured and error_analyzed event. That means Slack notifications, webhooks, and downstream observability tools all get the tag automatically without any extra wiring.

Why the badge matters

Two reasons. First, during triage, the origin tells you where to look. A client-side TypeError usually means a null guard missing in a render; a server-side one usually means an unexpected shape from an upstream service. The mental model you open the code with is different depending on where the failure happened.

Second, for ownership. In most teams, server and client errors are owned by different groups or, at minimum, handled in different rotations. The tag turns "whose is this?" into a one-glance question.

Interaction with the analyzer

Origin influences the analyzer indirectly. The analyzer reads the same error context regardless of origin, but the files fetched from the stack trace will naturally differ — a client-side stack fetches component files, a server-side stack fetches route handlers. The prompt itself doesn't branch on origin today; the signals baked into the stack are enough for the model to reason appropriately.

Filtering and search

Filtering the errors list by origin is on the roadmap. For now, the badge gives you the same information at a glance, and you can scan a long list quickly enough that dedicated filters aren't yet critical.

Edge cases

A few combinations worth noting. Edge functions and server-side-rendered components that fail during hydration are a grey area — the failure is on the client runtime but the code feels server-ish. These consistently tag as client because the runtime observable at failure time is the browser. Workers fall on the server side because they're Node. Serverless functions tag as server for the same reason.

If you disagree with a classification on a specific error, the explicit origin field in the SDK event is the override. Set it, and the inference layer gets out of the way.