OpenSEO Error Handling Patterns: A Complete Guide to Type-Safe Failure Management
OpenSEO uses a layered, type-safe error-handling architecture built around the AppError class, centralized middleware, and selective telemetry to PostHog.
The OpenSEO repository implements a disciplined approach to error handling in TypeScript that spans both server and client environments. Understanding these patterns is essential for contributing to the codebase or adapting similar strategies in your own projects.
Core Architecture: Four Layers of Error Handling
OpenSEO organizes error handling into distinct layers, each with a specific responsibility. This separation ensures business logic remains clean while failures are consistently tracked and safely exposed.
Domain Errors with AppError
At the foundation lies the AppError class defined in src/server/lib/errors.ts. This class represents business-logic failures using stable error codes rather than fragile string messages.
import { AppError } from '@/server/lib/errors';
throw new AppError('PROJECT_NOT_FOUND', `Project ${id} does not exist`);
The constructor signature requires:
- A code from the canonical
ERROR_CODESlist - An optional human-readable message
- Optional additional context
Validation Error Conversion
TanStack server-function validation failures are normalized through the isValidatorError helper in src/middleware/errorHandling.ts. This helper detects validator errors and transforms them into AppError instances with the VALIDATION_ERROR code.
Error Normalization Utilities
The same middleware file provides two critical conversion functions:
asAppError(error)— Attempts to coerce any thrown value to anAppError, returningnullif impossibletoClientError(appError)— Strips internal details to produce a safe, serializable error object
import { asAppError, toClientError } from '@/server/lib/errors';
try {
await riskyOperation();
} catch (e) {
const appError = asAppError(e) ?? new AppError('INTERNAL_ERROR');
throw toClientError(appError); // Safe for client consumption
}
Error Handling Middleware: The Central Guard
The errorHandlingMiddleware in src/middleware/errorHandling.ts wraps every server function to apply uniform error processing. It integrates with TanStack's middleware system via createMiddleware.
Execution Flow
- Wraps the downstream handler in a try/catch block
- Classifies caught errors (validator vs. unknown vs.
AppError) - Coerces non-
AppErrorvalues usingasAppError - Filters telemetry via
shouldCaptureAppErrorCode - Reports eligible errors to PostHog through
captureServerError - Sanitizes the response using
toClientErrorbefore re-throwing
Request Context Capture
When reporting occurs, the middleware gathers:
- HTTP method and path
- Resolved user distinct ID via
resolveErrorDistinctId - Authenticated state (without exposing tokens)
import { errorHandlingMiddleware } from '@/middleware/errorHandling';
import { createServerFn } from '@tanstack/react-start/server';
export const secureServerFn = createServerFn()
.use(errorHandlingMiddleware) // Installs full error stack
.handler(async (params) => {
// Your logic here; all errors handled uniformly
});
Global registration happens in src/serverFunctions/middleware.ts, ensuring every server function inherits this protection.
Telemetry Control and Observability
OpenSEO implements selective error reporting to avoid analytics noise. The src/shared/error-codes.ts file defines both the canonical error code list and the filtering logic.
Opting In or Out of Reporting
// src/shared/error-codes.ts
export const ERROR_CODES = [
'PROJECT_NOT_FOUND',
'VALIDATION_ERROR',
'AUTH_CONFIG_MISSING',
'CUSTOM_FEATURE_LIMIT_EXCEEDED',
// ... more codes
] as const;
export function shouldCaptureAppErrorCode(code?: string): boolean {
const BLOCKED = new Set(['AUTH_CONFIG_MISSING', 'RATE_LIMIT_SILENT']);
return !BLOCKED.has(code ?? '');
}
Only errors passing this filter reach captureServerError in src/server/lib/posthog.ts, which asynchronously sends structured data to PostHog inside waitUntil to avoid blocking responses.
Security: Client-Safe Error Exposure
The toClientError function enforces a critical boundary: internal details never leak to the client. The transformation:
- Preserves only the error code and safe message
- Removes stack traces
- Strips any internal context objects
- Falls back to a generic message for truly unexpected errors
This pattern satisfies security requirements while maintaining debugging capability through server-side logs and telemetry.
Key Files Reference
| File | Responsibility |
|---|---|
src/server/lib/errors.ts |
AppError class, asAppError, toClientError |
src/middleware/errorHandling.ts |
Central middleware, validator detection, request context |
src/shared/error-codes.ts |
Canonical codes, shouldCaptureAppErrorCode |
src/server/lib/posthog.ts |
captureServerError, PostHog integration |
src/serverFunctions/middleware.ts |
Global middleware registration |
Summary
- Type safety first: All errors flow through
AppErrorwith compile-time code validation - Middleware encapsulation:
errorHandlingMiddlewareprovides consistent behavior without boilerplate - Selective telemetry:
shouldCaptureAppErrorCodeprevents analytics pollution - Security by default:
toClientErrorguarantees safe client exposure - Observability: PostHog integration with request context enables effective debugging
Frequently Asked Questions
How does OpenSEO distinguish between validation and domain errors?
The isValidatorError helper in src/middleware/errorHandling.ts detects TanStack validator errors by checking error structure, then wraps them as AppError instances with code VALIDATION_ERROR. Domain errors are thrown directly as AppError instances from business logic.
What happens when an unexpected non-Error value is thrown?
The asAppError function attempts to extract meaningful information. If coercion fails, the middleware creates a generic AppError('INTERNAL_ERROR'). This ensures type safety even when third-party code throws primitives or unexpected objects.
Can I disable error reporting for specific error codes?
Yes. Add the code to the BLOCKED Set in src/shared/error-codes.ts within shouldCaptureAppErrorCode. Errors with blocked codes proceed through normal handling but skip the captureServerError telemetry step.
How does authentication affect error tracking?
The resolveErrorDistinctId function in the error handling middleware extracts the authenticated user's analytics ID when available. This attaches user context to PostHog events without exposing identifiers in client-facing responses.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →