Raw JavaScript errors exposed in the UI — TypeError: Cannot read properties of undefined, ECONNREFUSED, HTTP status codes — violate CWE-209 (information exposure) and OWASP A05 (Security Misconfiguration). Beyond security, they erode user trust: a non-technical user reading a stack trace has no path forward. Every technical error string that reaches a user represents a conversion failure, a support ticket, or churn. Error message quality is a measurable product metric, not a cosmetic concern.
High because raw error strings leak internal architecture to users and are direct evidence of OWASP A05 misconfiguration that also degrades perceived reliability.
Map every error category to a plain-language string in a central module. Never .toString() or .message an exception directly into JSX or toast notifications.
// lib/errors.ts
export function toUserMessage(err: unknown): string {
if (err instanceof Response || (err as any)?.status === 401) {
return 'Your session expired. Please log in again.'
}
if (err instanceof TypeError && (err.message.includes('fetch') || err.message.includes('network'))) {
return 'Unable to connect. Check your internet connection and try again.'
}
return 'Something went wrong. Please try again or contact support.'
}
Log the original err server-side at error level; surface only toUserMessage(err) in the UI.
ID: error-resilience.error-boundaries-ui-recovery.user-friendly-error-messages
Severity: high
What to look for: Count all error messages shown to users. Enumerate which use technical jargon (stack traces, error codes) vs. which use plain language. Search for error messages displayed to users in UI components, modals, toast notifications, and fallback screens. Check whether messages are in plain language or show raw error details (TypeError, network error codes, stack traces, etc.).
Pass criteria: All error messages shown to users are written in plain language and provide context or next steps. No raw JavaScript errors or stack traces are shown. 100% of user-facing error messages must be in plain language with no stack traces or technical error codes.
Fail criteria: Error messages displayed to users include raw JavaScript errors (TypeError, ReferenceError), HTTP status codes without explanation, stack traces, or technical jargon without context.
Skip (N/A) when: The application shows no user-facing errors (extremely unlikely for any production app).
Report even on pass: Report the error message patterns found: "X error messages analyzed; all use plain language."
Cross-reference: For sensitive data scrubbing in logs, see scrub-sensitive-logs.
Detail on fail: Give examples of the technical messages found. Example: "Form submission shows 'TypeError: Cannot read property of undefined' instead of 'Please check your email address'" or "API error displays 'ECONNREFUSED' instead of 'The server is currently unavailable. Please try again later.'"
Remediation: Map technical errors to user-friendly messages:
// lib/errors.ts — user-friendly messages
const USER_MESSAGES: Record<string, string> = {
'NETWORK_ERROR': 'Unable to connect. Please check your internet.',
'NOT_FOUND': 'We could not find what you were looking for.',
}
function getUserFriendlyMessage(error) {
if (error.message.includes('Network')) {
return 'Network error. Please check your connection and try again.'
}
if (error.message.includes('401') || error.message.includes('Unauthorized')) {
return 'Your session expired. Please log in again.'
}
if (error.message.includes('400') || error.message.includes('Bad Request')) {
return 'Please check your input and try again.'
}
if (error.message.includes('500') || error.message.includes('Server')) {
return 'The server is having trouble. Please try again later or contact support.'
}
return 'Something went wrong. Please try again.'
}
// Usage in a catch block:
catch (error) {
const message = getUserFriendlyMessage(error)
toast.error(message)
}