Observability

Error capture

Normalising errors and reporting them to PostHog.

Errors are normalised through a single helper, parseError, which returns a message you can show a user and reports the error in the background.

import { parseError } from '@kreogen/observability/error';

try {
  await doSomething();
} catch (error) {
  const message = parseError(error);

  return { error: message };
}

parseError never throws. Reporting is best-effort — a telemetry outage must not turn into a user-visible failure — and the message is returned regardless.

In the browser

Errors are captured to PostHog through posthog-js, which is already initialised by the analytics instrumentation. Nothing extra to configure: if NEXT_PUBLIC_POSTHOG_KEY is set, exceptions appear in PostHog.

The design system's handleError wraps this and shows a toast:

import { handleError } from '@kreogen/design-system/lib/utils';

<Button onClick={() => doSomething().catch(handleError)}>Save</Button>

On the server

Server errors are recorded through the structured logger rather than sent to PostHog directly. In a container, stdout is the log transport — the platform collector picks it up — so the log line is the durable record.

parseError is reachable from client components, so it must never import anything marked server-only. Reaching for the PostHog Node client here — even behind a dynamic import — pulls a server-only module into the client graph and fails the build. This has happened; it typechecks cleanly and only surfaces at build time.

If you want a specific server-side exception in PostHog, capture it explicitly where the server client is already safe to import:

import { analytics } from '@kreogen/analytics/server';

analytics?.captureException(error);

Error boundaries

Each app has a global-error.tsx that reports through parseError and renders a minimal recovery screen. Route-level error.tsx boundaries can do the same.