What kind of fallback UI would you design for different error types (404, 500, network, etc.)
Match the fallback to the error: 404 = friendly 'not found' with search + popular links; 500 = apologetic error with retry and a status link; network = inline retry with cached content if available; auth (401/403) = redirect to login / show 'no access'; chunk-load failure = soft reload prompt. Always: clear copy, a next action, no stack traces, log to monitoring.
A good fallback UI gives the user a path forward, never just "Error". The right design depends on the error class.
1. 404 — Not Found
The user navigated to something that doesn't exist. Friendly tone; offer recovery.
- Heading: "We couldn't find that page."
- Search bar + links to popular destinations / home.
- No stack trace, no apology spiral.
- Log so you can detect broken links (especially internal).
2. 500 / 5xx — Server Error
Something broke server-side; the user can't fix it. Apologize briefly; offer retry and status.
- "Something went wrong on our end."
- "Try again" button (with backoff).
- Link to status page if you have one.
- Capture the error id / trace id; show it so support can find it: "Reference: ab123…"
- Log to Sentry/Datadog with full context.
3. Network failure
The request never reached the server (or no response). Inline retry; show cached content if available.
- Toast or inline banner: "You're offline / Couldn't reach the server. Retry."
- Don't blow away the page — preserve scroll, form input.
- If a service worker has cached data, render that with a "stale" indicator.
4. Auth — 401 / 403
- 401 (not authenticated) — redirect to login, preserving the destination URL.
- 403 (no access) — "You don't have access to this resource" with a contact link / request access if applicable. Don't hint at whether the resource exists (security).
5. Chunk-load failure (code-split bundle)
A deploy invalidated a chunk URL the user's tab was about to fetch. The user sees a JS error and a blank screen — terrible UX.
- Catch in an error boundary; detect by error name (
ChunkLoadError). - Show a soft "We've updated this page. Reload to continue." prompt with a Reload button.
- Optionally auto-reload after a few seconds.
6. Validation / 400 errors
Inline next to the offending field. Never a global "Error" banner for a field error.
7. Empty states (not strictly errors)
Distinguish: "No results match these filters" is different from "Failed to load results". Both deserve their own design.
8. Patterns that apply across all
- Clear copy — no stack traces, no jargon, no "Whoops!".
- One primary action — Retry, Go Home, Login.
- Preserve context — don't unmount the whole app; scope the error boundary tight.
- Log to monitoring with breadcrumbs, user id (non-PII), and route.
- Accessibility —
role="alert"for important errors; don't hide errors visually only. - Don't auto-clear errors that need user action.
9. Architecture
- Route-level error boundaries for 404/500/auth.
- Component-level error boundaries around risky widgets (charts, embeds) so one widget failure doesn't kill the page.
- Network errors handled in the data layer (React Query
onError, Axios interceptor). - Chunk-load handled at the dynamic-import call site.
Interview framing
"Match the fallback to the error class. 404 is a navigation problem — friendly, with search and links. 500 is server-side — apologize, offer retry, surface a trace id for support. Network failure deserves inline retry with cached content if we have it. Auth 401 redirects to login preserving the destination; 403 is 'no access' without leaking existence. Chunk-load failures need a soft reload prompt — they happen after deploys. The shared rules: clear copy, one primary action, scoped error boundaries so one failure doesn't kill the app, and log everything to monitoring with enough context to debug."
Follow-up questions
- •Why distinguish 401 from 403 in the UI?
- •How do you handle chunk-load errors after a deploy?
- •Why scope error boundaries narrowly?
- •What goes into the error log to make it actionable?
Common mistakes
- •One generic 'Something went wrong' for every error class.
- •Showing a stack trace or 'undefined is not a function' to a user.
- •Unmounting the whole app on a widget error.
- •404 with no path forward.
- •Auto-clearing errors that need user action.
Performance considerations
- •Error boundaries shouldn't ship heavy code in the bundle — lazy-load the fallback's media/illustrations.
Edge cases
- •Offline → online recovery.
- •Login session expires mid-action.
- •Stale cached chunks after deploy.
- •Same error class for different root causes (timeout vs 500).
Real-world examples
- •GitHub's 404 octocat, Stripe's retry-friendly error toasts, Linear's auth-required redirect.