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.