A fallback that displays a raw stack trace violates CWE-209 (information exposure through error messages) and erodes user trust irreparably. Worse, a fallback with no retry or navigation option leaves users stranded — they cannot recover without a hard reload, and even then they may lose unsaved state. ISO 25010 reliability.recoverability requires the system to restore a defined state after failure; a blank or cryptic fallback achieves neither. User churn after an unrecoverable error is measurably higher than after a friendly one.
High because a non-actionable fallback UI converts a recoverable runtime error into a user-abandoned session, and raw error detail exposes internal implementation.
Replace bare error text with a fallback that includes a plain-language message, a retry action, and a navigation escape hatch. Never surface error.message or stack traces directly in the UI.
// components/error-fallback.tsx
export function ErrorFallback({
reset,
}: {
reset: () => void
}) {
return (
<div style={{ padding: '2rem', textAlign: 'center' }}>
<h2>We hit a snag</h2>
<p>Something unexpected happened. Your data is safe.</p>
<button onClick={reset}>Try again</button>
<a href="/">Go to home</a>
</div>
)
}
Log error.message server-side via your structured logger; show only a human-readable string in the UI.
ID: error-resilience.error-boundaries-ui-recovery.friendly-fallback-components
Severity: high
What to look for: Count all error boundary fallback UIs. Enumerate which provide helpful messages, retry buttons, and navigation options vs. which show generic "error" text. Examine the fallback UI rendered by error boundaries. Check whether the fallback includes: (1) a human-readable message (not a stack trace or error code), (2) a retry button or action the user can take, (3) navigation to home or safe area.
Pass criteria: Fallback components rendered by error boundaries are user-friendly: they show a clear message in plain language, include a retry action, and provide navigation back to a safe area (home, dashboard, etc.). At least 80% of error boundaries should have fallback UIs with a retry button and at least 1 navigation link.
Fail criteria: Fallback shows raw error details, stack trace, or error code; no retry button; no navigation option provided; or fallback is blank/minimal.
Skip (N/A) when: No error boundaries exist (caught by previous check).
Cross-reference: For user-friendly error messages, see user-friendly-error-messages.
Detail on fail: Describe what the fallback shows. Example: "Error boundary fallback shows raw TypeError with stack trace" or "Fallback displays error code but no retry button or navigation" or "Fallback says 'Error' with no context about what happened"
Remediation: Make fallback components helpful:
// components/error-fallback.tsx — friendly fallback
export function ErrorFallback({ error, reset }: { error: Error; reset: () => void }) {
return (<div><h2>Something went wrong</h2><p>Please try again.</p><button onClick={reset}>Retry</button><Link href="/">Go Home</Link></div>)
}
function ErrorFallback({ error, resetErrorBoundary }) {
return (
<div style={{ padding: '20px', textAlign: 'center' }}>
<h2>We encountered a problem</h2>
<p>We're sorry, but something unexpected happened. Try refreshing the page or go back to the dashboard.</p>
<div style={{ marginTop: '20px' }}>
<button onClick={resetErrorBoundary} style={{ marginRight: '10px' }}>
Try Again
</button>
<a href="/">Go to Home</a>
</div>
</div>
)
}