Lesson 27 of 42Article14 min
Handling Errors with error.tsx & notFound
If a Server Component throws, the nearest error.tsx shows a recovery UI. It must be a Client Component so the reset button can re-render the segment.
error.tsx — catch unexpected failures
If a Server Component throws, the nearest error.tsx shows a recovery UI. It must be a Client Component so the reset button can re-render the segment.
Real-life example: error.tsx is the fire exit sign — when the kitchen catches fire (crash), guests get a clear path instead of a blank wall (white screen).
app/dashboard/error.tsx
"use client";
export default function DashboardError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<main role="alert">
<h2>Something went wrong on the dashboard</h2>
<p>{error.message}</p>
<button type="button" onClick={() => reset()}>
Try again
</button>
</main>
);
}notFound() — missing resources
When an id or slug does not exist, call notFound(). Next.js renders the closest not-found.tsx (or the default 404).
Use notFound for expected missing data. Use throw / error.tsx for unexpected failures (DB down, bug, 500). Custom global UI lives in app/global-error.tsx for root layout crashes.
Triggering and customizing 404
import { notFound } from "next/navigation";
export default async function InvoicePage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const res = await fetch(`https://api.example.com/invoices/${id}`, {
cache: "no-store",
});
if (res.status === 404) notFound();
if (!res.ok) throw new Error("Failed to load invoice");
const invoice = await res.json();
return <h1>Invoice #{invoice.id}</h1>;
}
// app/invoices/[id]/not-found.tsx
export default function InvoiceNotFound() {
return (
<main>
<h2>Invoice not found</h2>
<p>Check the link or go back to the invoice list.</p>
</main>
);
}