The boundary hierarchy
app/global-error.tsx # catches root layout errors
app/layout.tsx
app/dashboard/error.tsx # catches dashboard page + below
app/dashboard/page.tsx
components
error.tsx — per-segment
Errors in the page or descendants bubble up to the nearest error.tsx. It must be a Client Component (uses hooks). Never catches errors in its own layout — put the boundary in the parent for that.
global-error.tsx — for root layout failures
Lives at app/global-error.tsx. Renders its own <html> and <body> because it replaces the broken root layout entirely.
Reporting
Wire error objects into Sentry / Datadog / your logger inside the boundary. The framework gives you an error.digest — that's the same id surfaced in server logs, so you can correlate user reports with traces.
Use the nearest error.tsx that can offer a meaningful recovery, reserve global-error.tsx for root failures, and use not-found.tsx only for an absent resource. Expected validation and authorization outcomes should be returned deliberately instead of thrown as accidental exceptions. A broad error boundary does not make failures handled. It can hide which segment owns recovery and tempt you to expose raw messages. Resetting retries the render; it does not repair a failed dependency or undo a partially completed mutation.
Trigger a render exception, missing record, rejected action, failed external dependency, and root-layout failure separately. Verify the intended boundary appears, sensitive details stay in server logs, a request ID connects UI to telemetry, and recovery actually succeeds.