Not Found UI
not-found.tsx. Placed inside a route segment, it renders whenever that segment is asked to show content that doesn't exist, replacing the generic default 404 page with something that matches your site.app/not-found.tsx
import Link from 'next/link'
export default function NotFound() {
return (
<div>
<h2>Page Not Found</h2>
<p>Could not find the requested resource.</p>
<Link href="/">Return Home</Link>
</div>
)
}Two ways it gets triggered
not-found.tsx renders in two distinct situations, and it's worth keeping them separate in your head.Trigger | When it happens |
|---|---|
Unmatched URL | The visitor requests a path with no matching route segment at all — Next.js falls back to the nearest |
Calling | Code running inside a route (often after a failed database lookup) explicitly decides the requested resource does not exist |
/posts/[slug] — still show a proper 404 when the specific slug doesn't correspond to real data.app/posts/[slug]/page.tsx
import { notFound } from 'next/navigation'
async function getPost(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}`)
if (!res.ok) return undefined
return res.json()
}
export default async function PostPage({
params,
}: {
params: { slug: string }
}) {
const post = await getPost(params.slug)
if (!post) {
notFound()
}
return <article>{post.title}</article>
}notFound() throws internally and immediately stops rendering the rest of the component — any code after the call never runs. Next.js catches that special error and renders the nearest not-found.tsx in its place, returning a proper 404 HTTP status.Root vs. segment-level not-found.tsx
not-found.tsx at the root of app/ acts as the site-wide fallback for any URL Next.js can't match at all. You can also add a more specific not-found.tsx inside a nested segment — for example app/posts/[slug]/not-found.tsx — so that a missing post gets a message tailored to "posts," distinct from a generic site-wide 404. When notFound() is called inside a segment, Next.js renders the closest not-found.tsx in that segment's tree, falling back up to the root one if none exists locally.Folder structure
app/
not-found.tsx ← site-wide fallback
posts/
[slug]/
page.tsx
not-found.tsx ← used when a specific post is missingnot-found.tsxrenders both for unmatched URLs and whenevernotFound()is called fromnext/navigation.notFound()immediately halts rendering of the current component — code after the call is unreachable.A response rendered via
not-found.tsxcorrectly returns an HTTP 404 status, not a 200.You can nest
not-found.tsxper segment for tailored messaging, with the root file acting as the site-wide fallback.