notFound

API Reference for the notFound function.

The notFound function throws an error that renders the not-found UI. It's useful for handling missing resources in your application. You can customize the UI using the not-found.js file.

Invoking notFound() throws a NEXT_HTTP_ERROR_FALLBACK;404 error and terminates rendering of the route segment where it was thrown. Next.js also injects a <meta name="robots" content="noindex" /> tag so the page is not indexed. Because it works by throwing, call it in the render path: a component, or a function a component awaits. A call left in an un-awaited promise throws where nothing catches it, and no not-found UI renders (in development the server logs ⨯ unhandledRejection: NEXT_HTTP_ERROR_FALLBACK;404).

notFound() can be invoked in Server Components, Server Functions, and Route Handlers.

import { notFound } from 'next/navigation'
 
async function fetchUser(id: string) {
  const res = await fetch('https://...')
  if (!res.ok) return undefined
  return res.json()
}
 
export default async function Profile({
  params,
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = await params
  const user = await fetchUser(id)
 
  if (!user) {
    notFound()
  }
 
  // ...
}

Good to know

You do not need to write return notFound(). Calling it is enough, because it throws an exception that stops function execution. TypeScript understands this from its never return type, so a value you check first stays narrowed afterward:

// fetchUser resolves to a user object, or undefined
const user = await fetchUser(id)
 
if (!user) {
  notFound()
}
 
// user is defined here
return <Profile user={user} />

Like any exception, it travels up the call stack until something catches it. A try/catch around the call suppresses it, and the not-found UI won't render. If you need to catch errors near the call, use unstable_rethrow to let the interrupt through first.

Examples

Calling notFound() after streaming has started

To keep a page's shell and loading UI visible while data loads, do the existence check inside a component wrapped in <Suspense> instead of blocking the whole route. The idiomatic place for the check is the data-access function itself, awaited by the component that needs the data:

import { Suspense } from 'react'
import Link from 'next/link'
import { notFound } from 'next/navigation'
 
async function getPost(slug: string) {
  const res = await fetch(`https://api.example.com/posts/${slug}`)
  if (res.status === 404) {
    notFound()
  }
  if (!res.ok) {
    throw new Error(`Failed to load post: ${res.status}`)
  }
  return res.json()
}
 
async function Article({ slug }: { slug: string }) {
  const post = await getPost(slug)
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  )
}
 
export default async function PostPage({ params }: PageProps<'/blog/[slug]'>) {
  const { slug } = await params
 
  return (
    <section>
      <Link href="/blog">Blog</Link>
      <Suspense fallback={<p>Loading...</p>}>
        <Article slug={slug} />
      </Suspense>
    </section>
  )
}

When the post doesn't exist, getPost calls notFound(), which throws. Because this happens during rendering, the exception propagates to the nearest not-found boundary, which renders in place of the streamed-in content, even though the page shell has already been sent.

Add a not-found.tsx alongside the route to define that UI. Without one, the nearest parent not-found boundary renders, falling back to Next.js's default 404 page:

export default function NotFound() {
  return (
    <section>
      <h1>Post not found</h1>
      <p>The post you're looking for doesn't exist.</p>
    </section>
  )
}

The response starts streaming from the <Suspense> boundary before notFound() runs, so it keeps its 200 status. Next.js adds a noindex tag.

notFound() returns a 404 when it runs before the response starts, including during prerendering. See Status codes.

Serving a 404 from a Route Handler

notFound() also works in a Route Handler, where it serves a 404 to the caller.

import { NextResponse } from 'next/server'
import { notFound } from 'next/navigation'
 
export async function GET(
  request: Request,
  { params }: RouteContext<'/api/posts/[slug]'>
) {
  const { slug } = await params
  const res = await fetch(`https://api.example.com/posts/${slug}`)
  if (!res.ok) {
    notFound()
  }
  return NextResponse.json(await res.json())
}

Version History

VersionChanges
v13.0.0notFound introduced.

On this page