Data and rendering

Handle errors and missing data

Treat validation failures and missing resources as expected outcomes while containing unexpected exceptions with route boundaries.

A missing note is not the same as a crashed database client. Expected outcomes should be represented deliberately instead of thrown as generic exceptions.

Call notFound() for an absent resource and provide not-found.tsx. Return structured validation results from actions. Add error.tsx as a Client Component for uncaught exceptions within a route segment. It receives a reset function for retrying. Log the underlying server error without exposing private details to the visitor.

An error boundary handles failures in the segment below it, not failures thrown by the layout at that same level. Move the boundary up when shared layout work can fail, or move risky work into a child segment. A global error boundary is the last resort for failures at the root.

Calling reset() asks React to render the segment again. It can recover from a temporary failure, but it cannot repair invalid configuration or a deterministic bug. Give the visitor another route out and preserve a server-side error identifier so operators can connect a friendly message to the real failure. In production, Server Component error details are intentionally sanitized.

// app/notes/[slug]/page.tsx
import { notFound } from 'next/navigation'

export default async function NotePage({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params
  const note = await getNote(slug)
  if (!note) notFound()
  return <h1>{note.title}</h1>
}

Visit /notes/does-not-exist after wiring not-found.tsx. You should see your custom missing page, not a stack trace. Trigger a separate temporary exception inside error.tsx and click reset. The segment should retry. Trigger a permanent bug in config and reset should not magically fix it.

Check the production response: no stack, query text, or secret in the HTML.

Throwing new Error('note missing') for a 404 sends users to error.tsx and feels like the app crashed. notFound() is the deliberate choice for an empty lookup result.

Lesson completed