~/TechPurAI
~/tutorials/nextjs-from-scratch/error-handling
intermediate·part 8 of 22·2 min read

Error handling: error.tsx and not-found.tsx

Updated Aug 16, 2026JavaScript · Next.js

Part 6's notFound() call has been running since then with no actual not-found.tsx to render — Next.js falls back to a generic, unstyled 404 in the meantime. This part adds a real one, plus the separate mechanism for an actual error (a thrown exception, not a missing post).

not-found.tsx: for notFound() and unmatched routes

tsx
// app/posts/[slug]/not-found.tsx
import Link from "next/link";

export default function PostNotFound() {
  return (
    <div>
      <h1>Post not found</h1>
      <p>That post doesn't exist, or may have been moved.</p>
      <Link href="/posts">Back to all posts</Link>
    </div>
  );
}

This renders whenever notFound() is called anywhere beneath app/posts/[slug]/, and also for a URL under this route that matches no page at all. Placing it here scopes it to the posts section specifically — a not-found.tsx at the project root (app/not-found.tsx) would instead be the fallback for the entire site.

error.tsx: catching an actual thrown error

tsx
// app/posts/[slug]/error.tsx
"use client";

export default function PostError({ error, reset }: { error: Error; reset: () => void }) {
  return (
    <div>
      <h1>Something went wrong loading this post.</h1>
      <button onClick={() => reset()}>Try again</button>
    </div>
  );
}

error.tsx is fundamentally different from not-found.tsx — it catches an actual thrown exception (a failed database call, a bug in a component) rather than a deliberate "this doesn't exist." It's always a Client Component (error.tsx requires "use client", unlike every other special file in this series so far), since React's error boundary mechanism — what error.tsx is built on — only exists on the client. reset() attempts to re-render the segment that failed, wired to the button here, without a full page reload.

Why these are two separate mechanisms

notFound() is a deliberate signal — "this specific thing genuinely isn't here," called on purpose, part of expected control flow. error.tsx catches something unexpected — a bug, a network failure, a database timeout. Conflating them (returning "not found" for an actual server error, or crashing the whole page for a missing post) sends the wrong signal to both the visitor and to anything monitoring the site for real failures.

What happens with neither in place

Without a route-specific error.tsx or not-found.tsx, Next.js falls back to the nearest one further up the tree, and eventually to its own generic built-in pages if none exist anywhere in the project. Adding scoped ones — like both of these, specific to the posts section — is what lets a 404 for a missing post look and read differently than, say, a 404 for a mistyped /about URL.

Common mistake

Forgetting "use client" on error.tsx and getting a build error that isn't obviously about that. It's the one required exception to "Server Components are the default" covered in part 3 — every error.tsx in a Next.js project needs it.

The blog's core reading experience is done — routing, data, loading states, error handling. Next: making it findable, with real SEO metadata instead of the generic title every page has shared since part 1.

VK

Vijay Kumar

Founder of TechPurAI — writing hands-on tutorials and honest tool breakdowns.

LinkedIn ↗
← previous7. Loading UI with loading.tsx and Suspensenext →9. The Metadata API: static and per-page SEO