# Error Handling Structured logging, typed custom errors, a React Error Boundary, and API route wrappers for Next.js 14 — gives every thrown error a consistent shape and HTTP status code. ## What's included **Logger** - `logger` — structured JSON logger with `debug`, `info`, `warn`, `error` methods; each entry includes `level`, `message`, `context`, `timestamp`, and a short `requestId`; production `error` calls include a commented hook to forward to Axiom/Datadog **Custom error classes** (all extend `AppError`) - `AppError` — base class; takes `message`, `code`, `statusCode`, optional `context`; has `toJSON()` for response serialization - `NotFoundError` — 404, code `NOT_FOUND`; accepts `resource` and optional `id` - `UnauthorizedError` — 401, code `UNAUTHORIZED` - `ForbiddenError` — 403, code `FORBIDDEN` - `ValidationError` — 422, code `VALIDATION_ERROR`; accepts a `fields` map for per-field errors - `RateLimitError` — 429, code `RATE_LIMITED`; stores `retryAfter` in context - `ExternalServiceError` — 502, code `EXTERNAL_SERVICE_ERROR`; prefixes message with service name **API route helpers** - `handleApiError(err)` — catches `AppError` subclasses and unknown errors; returns a typed `Response.json` with the right status code - `withErrorHandler(handler)` — wraps an async route handler in try/catch; calls `handleApiError` on throw; drop-in for `GET`, `POST`, etc. **Async utility** - `tryCatch(fn, context?)` — Go-style `[data, null] | [null, Error]` tuple; logs the error with optional context label before returning **React** - `ErrorBoundary` — class component; logs via `logger.error` in `componentDidCatch`; accepts a custom `fallback` component receiving `{ error, errorId, reset }`; falls back to `DefaultErrorFallback` if none provided - `DefaultErrorFallback` — renders error message + error ID + "Try Again" / "Go Home" buttons ## Setup ### 1. Install dependencies No extra packages — React and Next.js only. ### 2. Add to your project ```ts // Any API route import { withErrorHandler, NotFoundError } from '@/blocks/errorhandling' // Any client component tree import { ErrorBoundary } from '@/blocks/errorhandling' ``` ### 3. Wire up production logging (optional) In `logger._log`, uncomment and replace the `fetch('/api/log', ...)` line with your logging service's ingest endpoint. ## Usage examples ```ts // API route — withErrorHandler wraps the whole handler import { withErrorHandler, NotFoundError, ValidationError } from '@/blocks/errorhandling' export const GET = withErrorHandler(async (req) => { const block = await getBlock(id) if (!block) throw new NotFoundError('Block', id) return Response.json(block) }) export const POST = withErrorHandler(async (req) => { const body = await req.json() if (!body.email) throw new ValidationError('Invalid input', { email: 'Required' }) return Response.json({ ok: true }) }) ``` ```ts // tryCatch for async calls where you want to handle errors inline import { tryCatch, handleApiError } from '@/blocks/errorhandling' const [user, err] = await tryCatch(() => fetchUser(id), 'fetchUser') if (err) return handleApiError(err) ``` ```tsx // Wrap any subtree that might throw during render import { ErrorBoundary } from '@/blocks/errorhandling' function CustomFallback({ error, errorId, reset }) { return (
{error.message}