Frontend Error Handling
Frontend Error Handling — Every Failable Action Surfaces an Error
Section titled “Frontend Error Handling — Every Failable Action Surfaces an Error”No failable action may fail silently. Every network call, I/O, mutation, query,
or async action that can reject/throw MUST have a user-visible error path. A
swallowed error — empty catch, ignored isError, perpetual spinner, blank panel
— is a bug, not a style nit.
Required surface per kind
Section titled “Required surface per kind”- Mutations (TanStack Query): handle
onError→toast.error(...)(sonner; theToasteris mounted in__root.tsx). A mutation with no error path is incomplete. - Queries (TanStack Query): render the
isErrorstate explicitly — an inline error + retry affordance, NOT a perpetual spinner or empty content. Loading ≠ error. - Form submit / validation: field-level inline errors (react-hook-form +
FormMessage) for user-fixable input; a form-level error for the submit call itself. - Unexpected render/runtime errors: an error boundary (route or app level) with a visible fallback — never a white screen.
- Any imperative async (fetch, upload, action handler): wrap and surface the rejection — toast or inline.
Include the trace id
Section titled “Include the trace id”When the failing response carries an X-Trace-ID header, the trace id is part of
the error display’s design — render it per frontend-error-trace-id.md, which governs
WHERE it appears and the public-page / form-field exceptions. This rule mandates that
the error is shown at all; that rule mandates how the trace id rides along. Don’t
re-decide the split here.
Library-first
Section titled “Library-first”Use the existing surfaces — sonner toasts, react-hook-form FormMessage, shadcn
inline alerts — per frontend-conventions.md. Don’t hand-roll bespoke error UI.