Three special files, three behaviors
| File | What it does | Server or Client? |
|---|---|---|
loading.tsx | Wraps the page in Suspense; shown while data loads | Either |
error.tsx | Error boundary for the segment and below | Must be Client |
not-found.tsx | UI for notFound() calls in this segment | Either |
global-error.tsx for the root
An error.tsx can't catch errors in its own layout. The root layout has a special boundary: app/global-error.tsx. It must render its own <html> and <body> because it replaces the broken root layout.
Trigger 404 from anywhere
Call notFound() from next/navigation in a Server Component to bail out and render the nearest not-found.tsx. The framework handles the status code (404) for you.
Classify a failure before choosing its file. Waiting belongs to loading.tsx, an expected missing resource belongs to notFound(), and an unexpected exception belongs to error.tsx. That keeps user action, HTTP status, and operational reporting aligned. Inject a delay and an exception at each boundary, then record which layouts remain mounted. After recovery, verify that retry preserves the same input and navigation context. Turning every failure into a friendly 200 response hides incidents from crawlers and monitoring. Throwing validation failures into an error boundary is the opposite mistake: it destroys useful form context. Group failures by recovery behavior, not by the fact that they are inconvenient. Create one slow Promise, one missing ID, one child render exception, and one root-layout exception. Verify which boundary handles each case and which HTTP status leaves the server. Confirm global-error.tsx can render without the normal root shell.