NextjsNot Found UI

Not Found UI

Every app needs a way to tell a visitor "there's nothing here." The App Router gives you a dedicated file for exactly that: 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

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 not-found.tsx, or its built-in default if none exists

Calling notFound()

Code running inside a route (often after a failed database lookup) explicitly decides the requested resource does not exist

The second case is the more interesting one: it lets a dynamic route that matched the URL pattern — say /posts/[slug] — still show a proper 404 when the specific slug doesn't correspond to real data.

app/posts/[slug]/page.tsx

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>
}
Note
Calling 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
A 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

Text
app/
  not-found.tsx           ← site-wide fallback
  posts/
    [slug]/
      page.tsx
      not-found.tsx        ← used when a specific post is missing
  • not-found.tsx renders both for unmatched URLs and whenever notFound() is called from next/navigation.

  • notFound() immediately halts rendering of the current component — code after the call is unreachable.

  • A response rendered via not-found.tsx correctly returns an HTTP 404 status, not a 200.

  • You can nest not-found.tsx per segment for tailored messaging, with the root file acting as the site-wide fallback.