Next.js App Router uses a special not-found.tsx file to render custom 404 pages. Create app/not-found.tsx with a default-exported component that displays a styled error message, navigation links, and a search bar. V0 does not generate this file by default, so unmatched routes show the generic Next.js 404 page. You can also call the notFound() function from any Server Component to trigger the 404 page programmatically when data is not found, and create route-specific not-found.tsx files in subdirectories for different 404 designs per section.
Why V0 projects need a custom 404 page
V0 generates application pages but does not create a custom 404 page by default. When users visit a URL that does not match any route in the App Router file structure, Next.js shows its generic white 404 page with the text '404 | This page could not be found.' This looks unprofessional and provides no navigation back to working pages. In Next.js App Router, custom 404 pages are defined by creating a not-found.tsx file in the /app directory. This file is a Server Component that renders when no route matches or when the notFound() function is called from any page. You can also create a global error.tsx for runtime errors and route-specific not-found.tsx files for different sections of your app.
- V0 does not generate a not-found.tsx file by default, leaving the generic Next.js 404 page
- Users bookmark or share URLs to pages that have been removed or renamed
- Dynamic routes like /products/[id] need to show 404 when the ID does not match any data
- Crawlers and bots discover broken links that lead to unmatched routes
- The generic 404 page has no navigation, so users leave the site instead of finding what they need
Error messages you might see
404 | This page could not be found.This is the default Next.js 404 page shown when no not-found.tsx file exists. Create app/not-found.tsx to replace it with a custom design.
Error: NEXT_NOT_FOUND — This page could not be found. notFound() is triggered in a Server Component.The notFound() function was called from a page component, but no not-found.tsx file exists in the route's directory or any parent directory to handle the error.
Error: The default export is not a React Component in not-found: "/app/not-found"The not-found.tsx file must have a default export that is a React component. Named exports are not recognized.
Before you start
- A V0 project that needs a custom 404 page
- Access to the V0 code editor to create new files
- Understanding that not-found.tsx is a Server Component by default
How to fix it
Create app/not-found.tsx for a global custom 404 page
This file replaces the default Next.js 404 page for all unmatched routes in your application. It is a Server Component that can import shadcn/ui components and use your app's layout.
Create app/not-found.tsx for a global custom 404 page
This file replaces the default Next.js 404 page for all unmatched routes in your application. It is a Server Component that can import shadcn/ui components and use your app's layout.
Create a new file at app/not-found.tsx in the V0 file explorer. Export a default React component with your custom 404 design. The component automatically inherits the root layout.tsx, so your navigation and footer appear on the 404 page.
// No not-found.tsx — shows generic Next.js 404 page// app/not-found.tsximport Link from "next/link";import { Button } from "@/components/ui/button";export default function NotFound() { return ( <div className="flex flex-col items-center justify-center min-h-[60vh] px-6 text-center"> <h1 className="text-6xl font-bold">404</h1> <p className="mt-4 text-xl text-muted-foreground"> The page you are looking for does not exist. </p> <div className="mt-8 flex gap-4"> <Button asChild> <Link href="/">Go Home</Link> </Button> <Button variant="outline" asChild> <Link href="/contact">Contact Support</Link> </Button> </div> </div> );}Expected result: All unmatched routes show your custom 404 page with navigation links instead of the generic Next.js error.
Call notFound() in dynamic routes when data is missing
For dynamic routes like /products/[id], the URL structure is valid but the data may not exist. Calling notFound() from the page component triggers the nearest not-found.tsx to render with a proper 404 status code.
Call notFound() in dynamic routes when data is missing
For dynamic routes like /products/[id], the URL structure is valid but the data may not exist. Calling notFound() from the page component triggers the nearest not-found.tsx to render with a proper 404 status code.
In your dynamic route page component, check if the data exists after fetching. If it does not, call notFound() from next/navigation to trigger the 404 page.
// app/products/[id]/page.tsx — shows blank page for missing productsexport default async function ProductPage({ params }: { params: Promise<{ id: string }> }) { const { id } = await params; const product = await getProduct(id); // If product is null, renders nothing return <div>{product?.name}</div>;}// app/products/[id]/page.tsx — shows 404 for missing productsimport { notFound } from "next/navigation";export default async function ProductPage({ params }: { params: Promise<{ id: string }> }) { const { id } = await params; const product = await getProduct(id); if (!product) { notFound(); // Triggers not-found.tsx with 404 status } return <div>{product.name}</div>;}Expected result: Invalid product IDs show the custom 404 page with the correct HTTP 404 status code.
Style the 404 page to match your application design
A well-designed 404 page keeps users engaged by matching the app's visual style and providing clear navigation options to find what they are looking for.
Style the 404 page to match your application design
A well-designed 404 page keeps users engaged by matching the app's visual style and providing clear navigation options to find what they are looking for.
Use shadcn/ui components, your project's design tokens, and Tailwind utilities to create a 404 page that feels like part of the application. Include navigation links, a search bar, and suggested pages.
// Basic unstyled 404 pageexport default function NotFound() { return <h1>Page not found</h1>;}// Styled 404 page with search and suggested linksimport Link from "next/link";import { Button } from "@/components/ui/button";import { Input } from "@/components/ui/input";import { Search } from "lucide-react";export default function NotFound() { return ( <div className="flex flex-col items-center justify-center min-h-[60vh] px-6"> <div className="text-center max-w-md"> <p className="text-sm font-medium text-primary">404 Error</p> <h1 className="mt-2 text-3xl font-bold">Page not found</h1> <p className="mt-4 text-muted-foreground"> Sorry, we could not find the page you are looking for. </p> <div className="mt-6 flex items-center gap-2 max-w-sm mx-auto"> <Input placeholder="Search..." /> <Button size="icon"><Search className="h-4 w-4" /></Button> </div> <div className="mt-8 flex flex-col gap-2"> <p className="text-sm font-medium">Popular pages:</p> <div className="flex gap-3 justify-center"> <Button variant="link" asChild><Link href="/">Home</Link></Button> <Button variant="link" asChild><Link href="/products">Products</Link></Button> <Button variant="link" asChild><Link href="/blog">Blog</Link></Button> </div> </div> </div> </div> );}Expected result: The 404 page matches the app's design and provides search and navigation to help users find content.
Add route-specific not-found.tsx for different app sections
Different sections of your app may need different 404 pages. A blog section might suggest recent posts, while a dashboard section might redirect to the main dashboard. Next.js uses the nearest not-found.tsx in the directory tree.
Add route-specific not-found.tsx for different app sections
Different sections of your app may need different 404 pages. A blog section might suggest recent posts, while a dashboard section might redirect to the main dashboard. Next.js uses the nearest not-found.tsx in the directory tree.
Create a not-found.tsx file inside the route directory for sections that need a custom 404 design. This file overrides the global not-found.tsx for all routes within that directory.
// Only one global not-found.tsx for the entire app// app/blog/not-found.tsx — blog-specific 404import Link from "next/link";import { Button } from "@/components/ui/button";export default function BlogNotFound() { return ( <div className="max-w-2xl mx-auto px-6 py-16 text-center"> <h1 className="text-3xl font-bold">Post not found</h1> <p className="mt-4 text-muted-foreground"> This blog post may have been moved or deleted. </p> <Button asChild className="mt-6"> <Link href="/blog">Browse All Posts</Link> </Button> </div> );}Expected result: Missing blog posts show a blog-specific 404 page, while other sections use the global 404 page.
Complete code example
1import Link from "next/link";2import { Button } from "@/components/ui/button";3import { Card, CardContent } from "@/components/ui/card";4import { Home, ArrowLeft, Search } from "lucide-react";56export default function NotFound() {7 return (8 <div className="flex flex-col items-center justify-center min-h-[60vh] px-6">9 <Card className="max-w-md w-full text-center">10 <CardContent className="pt-10 pb-8 px-8">11 <div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-muted mb-6">12 <Search className="h-8 w-8 text-muted-foreground" />13 </div>14 <h1 className="text-4xl font-bold">404</h1>15 <p className="mt-3 text-muted-foreground">16 We could not find the page you were looking for.17 It may have been moved or no longer exists.18 </p>19 <div className="mt-8 flex flex-col sm:flex-row gap-3 justify-center">20 <Button asChild>21 <Link href="/">22 <Home className="mr-2 h-4 w-4" />23 Go Home24 </Link>25 </Button>26 <Button variant="outline" asChild>27 <Link href="javascript:history.back()">28 <ArrowLeft className="mr-2 h-4 w-4" />29 Go Back30 </Link>31 </Button>32 </div>33 </CardContent>34 </Card>35 </div>36 );37}Best practices to prevent this
- Always create app/not-found.tsx in V0 projects — the default Next.js 404 page looks unprofessional and provides no navigation
- Call notFound() in dynamic route pages when the data for the requested parameter does not exist
- Include navigation links and a search bar on the 404 page to help users find the content they were looking for
- Use the same layout (navigation, footer) on the 404 page by letting it inherit from layout.tsx
- Create route-specific not-found.tsx files for sections like blog, docs, or products that benefit from contextual 404 messages
- For complex error handling strategies, RapidDev can implement custom 404, 500, and error boundary pages across your V0 app
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
My V0 Next.js App Router project shows the generic '404 | This page could not be found' page. How do I create a custom 404 page that matches my app's design, includes navigation links, and shows proper 404 status codes for missing dynamic route data?
Frequently asked questions
How do I create a custom 404 page in V0?
Create a file at app/not-found.tsx in the V0 code editor. Export a default React component with your custom 404 design. It automatically inherits your root layout.tsx, so navigation and footer appear on the 404 page.
What is the difference between not-found.tsx and error.tsx?
not-found.tsx handles 404 errors when a route does not exist or notFound() is called. error.tsx handles runtime errors like failed API calls or unhandled exceptions during page rendering. They serve different purposes and both should be created.
Can I have different 404 pages for different sections of my app?
Yes. Create a not-found.tsx in any route directory to override the global 404 page for that section. For example, app/blog/not-found.tsx shows a blog-specific 404, while app/not-found.tsx handles all other routes.
How do I trigger a 404 for a dynamic route when the data does not exist?
Import notFound from next/navigation and call it in your page component when the fetched data is null or undefined. Next.js will render the nearest not-found.tsx with a proper HTTP 404 status code.
Does the custom 404 page inherit the layout?
Yes. The not-found.tsx file inherits the nearest layout.tsx in the directory tree. Your global navigation and footer from app/layout.tsx will appear on the 404 page automatically.
Can RapidDev help create custom error pages for my V0 project?
Yes. RapidDev can design and implement custom 404, 500, and error boundary pages that match your brand, include analytics tracking for broken links, and provide helpful navigation to keep users engaged.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your issue.
Book a free consultation