Frontend Error Handling: Boundaries, Logging, and Recovery

Most frontend code assumes happy paths. Production reality is messier — APIs fail, networks drop, user devices crash, browsers behave unpredictably. The interview probes whether you understand error handling as a systematic concern, not an afterthought.

The categories of error

  • Network errors: request fails, times out, returns non-200
  • Application errors: JavaScript exception in user code
  • Validation errors: user input does not match expectations
  • Authorization errors: 401/403 responses
  • Browser errors: ResizeObserver loop, quota exceeded, etc.

React error boundaries

Class components with componentDidCatch catch errors in their child tree:

class ErrorBoundary extends React.Component {
  state = { hasError: false };
  static getDerivedStateFromError() { return { hasError: true }; }
  componentDidCatch(error, info) { logError(error, info); }
  render() { return this.state.hasError ? <Fallback /> : this.props.children; }
}

Wrap top-level routes and risky subtrees. Without boundaries, errors crash the entire app.

Where to put boundaries

  • Around the entire app (catches crashes, shows fallback)
  • Around each route
  • Around third-party widgets
  • Around isolated features that can fail independently

Don’t boundary every component — performance and UX cost.

Network error handling

Use a data layer that handles errors:

  • TanStack Query has built-in retry with exponential backoff
  • React Query gives you isError, error, refetch in hooks
  • Manual fetch needs explicit try/catch + retry logic

Best practice: render a meaningful error state with a retry button, not a blank screen.

Distinguish error types

Different errors need different UX:

  • Network failure: “Connection lost; tap to retry”
  • 5xx server error: “Something went wrong on our end”
  • 4xx client error: show specific message (“This item was not found”)
  • 401: redirect to login
  • 403: “You do not have access”

Retry logic

For transient errors (network, 5xx):

  • Retry with exponential backoff (1s, 2s, 4s, 8s)
  • Cap at 3–5 attempts
  • Don’t retry idempotent vs non-idempotent the same way (do not retry POSTs blindly)

For permanent errors (4xx), do not retry — show the user.

Optimistic updates and rollback

If a write fails:

  1. Local state had been updated optimistically
  2. Network call fails
  3. Roll back local state to pre-write
  4. Show error toast with retry option

Without rollback, UI lies — shows success when reality is failure.

Global error logging

Capture errors in production:

  • window.onerror for unhandled exceptions
  • window.onunhandledrejection for unhandled promise rejections
  • React error boundaries’ componentDidCatch

Send to Sentry, Bugsnag, Datadog RUM, or your own backend. Include user context, breadcrumbs, and source maps.

Source maps in production

Without source maps, error reports show minified gibberish. Upload source maps to your error tracker; never serve them publicly (they expose source code).

Track user actions leading up to the error:

  • Page navigations
  • API calls (just URLs, not bodies)
  • UI interactions (button clicks)
  • State changes

When the error fires, breadcrumbs help reproduce.

Fallback UI

Bad fallback: “Something went wrong” with no actionable info.

Good fallback:

  • Specific to the failure (“Could not load posts”)
  • Actionable (Retry button)
  • Preserves context (don’t crash the entire page if one widget failed)
  • Never blank — always show something

Common antipatterns

  • Generic “Error” toast for every failure
  • No retry mechanism
  • White screen of death (no error boundary at root)
  • Silent error swallowing (catch + ignore)
  • Logging errors to console only (lost in production)

Frequently Asked Questions

Should I show the actual error message to users?

For dev errors: usually no. For network or validation errors: yes if useful. Generic message + retry is the safe default.

What about service errors during streaming responses?

Streaming (SSE, LLM token streams) need different handling. Show partial output, then error message, then retry option.

How do I prevent error infinite loops?

Error boundaries should not re-render their fallback in a way that triggers errors. Test fallbacks deliberately.

Scroll to Top