An error boundary that returns null or an unstyled error dump gives users a white screen with no explanation and no recovery path, which is functionally identical to the crash it was meant to contain. The fallback is the last line of UX defense when a render throws, and a null return hides the reset() hook that would let the user recover without a full reload. WCAG 2.2 SC 3.3.1 requires that error identification be perceivable, which a blank fallback fails by definition.
High because a broken fallback strands every user who hits an exception with no self-service recovery and forces a full page reload.
Replace null returns and raw error.message dumps with a fallback that names the failure in plain language and wires a recovery button to the reset() prop. Keep segment-level boundaries narrow so one broken section does not replace the whole viewport. Edit app/error.tsx or app/global-error.tsx:
'use client'
export default function Error({ reset }: { error: Error; reset: () => void }) {
return (
<div className="flex flex-col items-center gap-4 p-8">
<h1>Something went wrong</h1>
<button onClick={() => reset()}>Try again</button>
<a href="/">Go home</a>
</div>
)
}
ID: saas-error-handling.error-boundaries.error-boundaries-fallback-ui
Severity: high
What to look for: Examine error boundary components for the quality of their fallback UI. Look at app/error.tsx, app/global-error.tsx, pages/_error.tsx, and any component-level error boundaries. Evaluate: (1) Does the fallback render any visible UI at all (not a null/empty return)? (2) Does it communicate that something went wrong in plain language? (3) Does it offer a recovery action — a "Try again" button that calls reset(), a link back to the home page, or a support contact? (4) Is the fallback visually coherent (not a raw HTML dump or unstyled text on a white screen)?
Pass criteria: Count all error boundary fallback components. Pass if error boundaries render a fallback that: is not null/empty, includes a human-readable explanation of the failure, and provides at least 1 recovery action (reset button, home link, or support contact). Extract and quote the fallback JSX from the first error boundary to verify it meets these criteria. Report even on pass: "X error boundary fallbacks examined, all provide recovery actions."
Fail criteria: Fail if error boundaries return null or an empty fragment. Fail if the fallback is only a technical error dump (renders error.message verbatim). Do not pass when a reset button exists but the fallback renders no explanation — users need context before deciding to retry.
Skip (N/A) when: The project has no React dependency. Signal: same as react-error-boundary check above.
Detail on fail: Describe the specific deficiency (e.g., "global-error.tsx returns null for fallback; route error.tsx has a message but no reset() button or home link"). Max 500 chars.
Remediation: An error boundary with a poor fallback is nearly as bad as no boundary — users still see a broken experience and have no path forward.
A good fallback answers three questions: What happened? Is it my fault? What can I do?
'use client'
export default function Error({ reset }: { error: Error; reset: () => void }) {
return (
<div className="flex flex-col items-center justify-center min-h-screen gap-4">
<h1 className="text-2xl font-semibold">Something went wrong</h1>
<p className="text-muted-foreground">
An unexpected error occurred. Our team has been notified.
</p>
<div className="flex gap-3">
<button onClick={() => reset()} className="btn-primary">Try again</button>
<a href="/" className="btn-secondary">Go home</a>
</div>
</div>
)
}
For segment-level boundaries (individual page sections), consider offering a fallback that keeps the rest of the page functional rather than replacing the whole viewport.