← Back to docs

404 Route Handling

Patterns for graceful not-found responses in Next.js App Router.

Built-in not-found()

Call notFound() inside a server component or route handler. Next.js renders the nearest not-found.tsx boundary.

// app/products/[slug]/page.tsx
import { notFound } from "next/navigation";

export default async function Page({ params }) {
  const product = await getProduct(params.slug);
  if (!product) notFound();
  return <ProductDetail product={product} />;
}

Route handler 404

Return a plain Response with status 404. No redirect needed.

// app/api/users/[id]/route.ts
export async function GET(_req, { params }) {
  const user = await db.user.findUnique({ where: { id: params.id } });
  if (!user) {
    return Response.json({ error: "Not found" }, { status: 404 });
  }
  return Response.json(user);
}

Global not-found.tsx

Place at the app root for a catch-all 404 UI. Works for unmatched routes and manual notFound() calls.

// app/not-found.tsx
import Link from "next/link";

export default function NotFound() {
  return (
    <div className="flex min-h-screen flex-col items-center justify-center">
      <h1 className="text-6xl font-bold text-[#8B5CF6]">404</h1>
      <p className="mt-4 text-gray-400">Page not found</p>
      <Link href="/" className="mt-6 text-[#F472B6] hover:underline">
        Go home
      </Link>
    </div>
  );
}

Per-segment boundaries

Nest not-found.tsx inside route groups for scoped 404 pages. The nearest boundary wins.