Error handling: error.tsx and not-found.tsx
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
// 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
// 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.
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.