Graceful error handling in the App Router with error.tsx for runtime errors, not-found.tsx for 404s, and recovery patterns. Real examples from my portfolio.
error.tsx
Client Component ('use client' required).
Receives { error, reset }.
Shows when a runtime error occurs in its route segment subtree.
reset() re-renders the segment to attempt recovery.
Can log errors (e.g., to an external service) and optionally show error.digest in production.
not-found.tsx
Server Component by default.
Used for 404 states.
Shown when:
A route does not exist, or
You call notFound() from next/navigation (e.g., when data is missing).
global-error.tsx
Client Component at the root of app/.
Catches errors in the root layout and anything not handled by nested error boundaries.
Must render its own <html> and <body> tags.
Hierarchy
Error boundaries are scoped by route segment:
app/error.tsx → global for all routes.
app/(marketing)/error.tsx → only for (marketing) routes.
app/(marketing)/projects/error.tsx → only for projects under marketing.
The closest error boundary to the erroring component wins.
Best Practices
Provide recovery actions: reset() and navigation (e.g., Home button).
Log errors in useEffect in error.tsx / global-error.tsx.
Avoid exposing stack traces; rely on error.digest for user-facing IDs.
Treat error and 404 pages as part of UX: clear messaging, consistent styling.
Offer helpful links (home, key sections) to keep users in the flow.