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

Adding custom pages or dynamic routes in V0

V0 uses Next.js App Router where routes are defined by the folder structure under /app. To add a new static page, create a directory with a page.tsx file. For dynamic routes like /blog/my-post, use bracket notation like [slug]/page.tsx. V0's AI can generate new pages via prompts, but you can also create them manually in the code editor. Each route can have its own layout.tsx, loading.tsx, and error.tsx for shared UI, loading states, and error handling.

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

V0 uses Next.js App Router where routes are defined by the folder structure under /app. To add a new static page, create a directory with a page.tsx file. For dynamic routes like /blog/my-post, use bracket notation like [slug]/page.tsx. V0's AI can generate new pages via prompts, but you can also create them manually in the code editor. Each route can have its own layout.tsx, loading.tsx, and error.tsx for shared UI, loading states, and error handling.

Why adding pages requires understanding App Router file conventions

V0 generates Next.js App Router projects where routing is entirely file-based — there is no routes configuration file. Each folder under /app that contains a page.tsx becomes a route. Dynamic segments use bracket notation like [id] or [slug], and catch-all routes use [...slug]. This differs from adding individual pages because you need to understand the file conventions to create routes that work with layouts, loading states, and metadata. V0's AI can add pages via prompts, but it sometimes creates them in the wrong location or misses the page.tsx file convention, resulting in 404 errors.

  • New directory created under /app but missing the page.tsx file, so the route returns 404
  • Dynamic route uses incorrect bracket notation (parentheses or curly braces instead of square brackets)
  • Page component is not the default export, which Next.js App Router requires
  • Route group folders using (groupName) syntax are confused with dynamic route folders
  • V0 generates a page inside the wrong directory level in the /app folder structure

Error messages you might see

404: This page could not be found.

The route does not have a page.tsx file in the correct directory. Check that the file exists at the exact path matching the URL: /app/about/page.tsx for the /about route.

Error: Page "/products/[id]" is missing "generateStaticParams()" so it cannot be used with "output: export".

When using static export mode, every dynamic route needs a generateStaticParams function that returns all valid parameter values at build time.

Error: The default export is not a React Component in page: "/app/about/page"

Next.js App Router requires the page.tsx file to have a default export that is a React component. Named exports are not recognized as the page component.

Before you start

  • A V0 project where you need to add new pages or dynamic routes
  • Access to the V0 code editor to create files and directories
  • Understanding of URL structure and how it maps to file paths

How to fix it

1

Create a new static page by adding a directory with page.tsx

In Next.js App Router, each route corresponds to a directory under /app that contains a page.tsx file. The directory name becomes the URL segment.

In the V0 file explorer, right-click on the /app directory and create a new folder (e.g., 'about'). Inside that folder, create a page.tsx file with a default-exported React component.

Before
typescript
// No /about route exists
// app/
// page.tsx (home page only)
After
typescript
// app/about/page.tsx — creates the /about route
export default function AboutPage() {
return (
<div className="max-w-4xl mx-auto px-6 py-16">
<h1 className="text-3xl font-bold">About Us</h1>
<p className="mt-4 text-muted-foreground">
Learn more about our team and mission.
</p>
</div>
);
}

Expected result: Navigating to /about displays the new About page.

2

Add a dynamic route with bracket notation for parameters

Dynamic routes like /blog/my-first-post use a directory named with square brackets [slug] to capture the URL parameter. The parameter value is available as a prop in the page component.

Create a directory with square brackets in the name (e.g., [slug]) inside the parent route directory. Add a page.tsx that receives the params prop.

Before
typescript
// Only a static /blog page exists
// app/blog/page.tsx
After
typescript
// app/blog/[slug]/page.tsx — creates /blog/:slug routes
export default async function BlogPost({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
return (
<article className="max-w-3xl mx-auto px-6 py-16">
<h1 className="text-3xl font-bold capitalize">
{slug.replace(/-/g, " ")}
</h1>
</article>
);
}

Expected result: Navigating to /blog/my-first-post displays the page with 'my first post' as the heading.

3

Add metadata and loading states to new pages

Each route in App Router can export a metadata object for SEO and a loading.tsx file for skeleton screens during data fetching. Adding these improves user experience and search engine visibility.

Export a metadata object from your page.tsx for static metadata, or use generateMetadata for dynamic metadata based on route parameters. Create a loading.tsx file in the same directory for a loading skeleton.

Before
typescript
// app/blog/[slug]/page.tsx — no metadata or loading state
export default async function BlogPost({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
return <h1>{slug}</h1>;
}
After
typescript
// app/blog/[slug]/page.tsx — with dynamic metadata
import type { Metadata } from "next";
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
const { slug } = await params;
return {
title: slug.replace(/-/g, " "),
description: `Read our blog post about ${slug.replace(/-/g, " ")}`,
};
}
export default async function BlogPost({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
return (
<article className="max-w-3xl mx-auto px-6 py-16">
<h1 className="text-3xl font-bold capitalize">
{slug.replace(/-/g, " ")}
</h1>
</article>
);
}

Expected result: The browser tab shows the dynamic page title, and a loading skeleton displays while data is fetched.

4

Create a catch-all route for flexible URL patterns

Catch-all routes use [...slug] syntax to match multiple URL segments. This is useful for documentation pages, nested category structures, or any URL pattern with variable depth.

Create a directory named [...slug] and access the segments as an array in the page component. Use optional catch-all [[...slug]] to also match the parent route.

Before
typescript
// Only matches /docs — not /docs/getting-started or /docs/api/auth
// app/docs/page.tsx
After
typescript
// app/docs/[...slug]/page.tsx — matches /docs/anything/at/any/depth
export default async function DocsPage({
params,
}: {
params: Promise<{ slug: string[] }>;
}) {
const { slug } = await params;
const path = slug.join("/");
return (
<div className="max-w-4xl mx-auto px-6 py-16">
<p className="text-sm text-muted-foreground">docs/{path}</p>
<h1 className="text-3xl font-bold mt-2">
{slug[slug.length - 1].replace(/-/g, " ")}
</h1>
</div>
);
}

Expected result: URLs like /docs/getting-started, /docs/api/auth, and /docs/guides/deployment all render correctly.

Complete code example

app/products/[id]/page.tsx
1import type { Metadata } from "next";
2import { notFound } from "next/navigation";
3import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
4import { Badge } from "@/components/ui/badge";
5
6interface Product {
7 id: string;
8 name: string;
9 description: string;
10 price: number;
11 category: string;
12}
13
14async function getProduct(id: string): Promise<Product | null> {
15 try {
16 const res = await fetch(`${process.env.API_URL}/products/${id}`);
17 if (!res.ok) return null;
18 return res.json();
19 } catch {
20 return null;
21 }
22}
23
24export async function generateMetadata({
25 params,
26}: {
27 params: Promise<{ id: string }>;
28}): Promise<Metadata> {
29 const { id } = await params;
30 const product = await getProduct(id);
31 return {
32 title: product?.name ?? "Product Not Found",
33 description: product?.description ?? "",
34 };
35}
36
37export default async function ProductPage({
38 params,
39}: {
40 params: Promise<{ id: string }>;
41}) {
42 const { id } = await params;
43 const product = await getProduct(id);
44
45 if (!product) notFound();
46
47 return (
48 <div className="max-w-3xl mx-auto px-6 py-16">
49 <Card>
50 <CardHeader>
51 <div className="flex items-center justify-between">
52 <CardTitle>{product.name}</CardTitle>
53 <Badge>{product.category}</Badge>
54 </div>
55 </CardHeader>
56 <CardContent>
57 <p className="text-muted-foreground">{product.description}</p>
58 <p className="text-2xl font-bold mt-4">${product.price}</p>
59 </CardContent>
60 </Card>
61 </div>
62 );
63}

Best practices to prevent this

  • Always use default exports for page components — Next.js App Router does not recognize named exports as pages
  • Name dynamic route directories with square brackets like [id] or [slug] — not curly braces or parentheses
  • Use route groups with parentheses (marketing) for organizational folders that should not create URL segments
  • Add generateMetadata to every page for proper SEO — V0 often generates pages without metadata
  • Create loading.tsx alongside page.tsx for any route that fetches data to show a skeleton during loading
  • Call notFound() from the page component when a dynamic route parameter does not match any data
  • For complex routing structures, consider having RapidDev architect the route hierarchy before generating pages with V0

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I need to add a dynamic product detail page at /products/[id] in my V0 Next.js App Router project. It should fetch product data, show a 404 if the product is not found, and have dynamic metadata for SEO. How do I set up the file structure?

Frequently asked questions

How do I add a new page to my V0 project?

Create a new directory under /app with the route name and add a page.tsx file inside it. For example, to create /about, add app/about/page.tsx with a default-exported React component. The page appears immediately in the V0 preview.

How do dynamic routes work in V0 Next.js projects?

Create a directory with square brackets like [id] or [slug] under the parent route. The bracket name becomes a parameter available in the page component via the params prop. For example, app/products/[id]/page.tsx handles /products/1, /products/2, etc.

What is the difference between [slug] and [...slug] in App Router?

Single brackets [slug] match one URL segment (/blog/my-post). Spread brackets [...slug] match multiple segments at any depth (/docs/api/auth/session). Optional catch-all [[...slug]] also matches the parent route (/docs).

Why does my new V0 page return a 404?

The most common cause is a missing page.tsx file. The directory exists but does not contain a page.tsx with a default export. Check the file name spelling and ensure the component is exported as default, not as a named export.

Can I ask V0 to generate new pages automatically?

Yes. Prompt V0 with specific instructions like 'Create a /pricing page with a three-tier pricing table using shadcn/ui Card components.' V0 creates the correct file structure and component. Review the result to ensure the file is in the right directory.

Can RapidDev help architect the routing structure for my V0 app?

Yes. RapidDev can design the complete route hierarchy including dynamic routes, route groups, parallel routes, and intercepting routes — then implement them in your V0 project with proper layouts, loading states, and error handling.

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.