Skip to main content
RapidDev - Software Development Agency
v0-issues

Creating and styling custom 404 pages in V0

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.

Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced8 min read10-20 minutesV0 with Next.js App Router 14+March 2026RapidDev Engineering Team
TL;DR

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

1

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.

Before
typescript
// No not-found.tsx — shows generic Next.js 404 page
After
typescript
// app/not-found.tsx
import 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.

2

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.

Before
typescript
// app/products/[id]/page.tsx — shows blank page for missing products
export 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>;
}
After
typescript
// app/products/[id]/page.tsx — shows 404 for missing products
import { 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.

3

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.

Before
typescript
// Basic unstyled 404 page
export default function NotFound() {
return <h1>Page not found</h1>;
}
After
typescript
// Styled 404 page with search and suggested links
import 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.

4

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.

Before
typescript
// Only one global not-found.tsx for the entire app
After
typescript
// app/blog/not-found.tsx — blog-specific 404
import 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

app/not-found.tsx
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";
5
6export 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 Home
24 </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 Back
30 </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.

ChatGPT Prompt

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your issue.

Book a free consultation

Need help with your Lovable project?

Our experts have built 600+ apps and can solve your issue fast. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.