Back to Next Js

notFound

docs/01-app/03-api-reference/04-functions/not-found.mdx

16.3.07.4 KB
Original Source

The notFound function throws an error that renders a Next.js 404 page. 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.

tsx
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()
  }

  // ...
}
jsx
import { notFound } from 'next/navigation'

async function fetchUser(id) {
  const res = await fetch('https://...')
  if (!res.ok) return undefined
  return res.json()
}

export default async function Profile({ params }) {
  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:

tsx
// 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:

tsx
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>
  )
}
jsx
import { Suspense } from 'react'
import Link from 'next/link'
import { notFound } from 'next/navigation'

async function getPost(slug) {
  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 }) {
  const post = await getPost(slug)
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  )
}

export default async function PostPage({ params }) {
  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:

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

The trade-off is the HTTP status code. Because the check runs inside the <Suspense> boundary, the response has already begun streaming as a 200, and the status can't change once streaming has started. The noindex tag keeps a soft 404 out of search results. To return a real 404 status, the resource has to be checked before the response streams. With Cache Components, every dynamic route streams a static shell first, so run that check in proxy instead. 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.

tsx
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())
}
jsx
import { NextResponse } from 'next/server'
import { notFound } from 'next/navigation'

export async function GET(request, { params }) {
  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.