---
title: "Dynamic Routes & Static Generation"
description: "Use Next.js App Router dynamic routes to create individual product pages. Implement generateStaticParams for static generation at build time, ensuring fast page loads."
canonical_url: "https://vercel.com/academy/ai-summary-app-with-nextjs/dynamic-routes-static-generation"
md_url: "https://vercel.com/academy/ai-summary-app-with-nextjs/dynamic-routes-static-generation.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-04-11T11:43:53.544Z"
content_type: "lesson"
course: "ai-summary-app-with-nextjs"
course_title: "Creating an AI Summary App with Next.js"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Dynamic Routes & Static Generation

# Dynamic routes & static generation

Dynamic routes let you create pages programmatically without manually creating files. With `generateStaticParams`, Next.js pre-renders all product pages at build time. Result: instant page loads with zero server computation.

## Outcome

Create dynamic product pages at `/[productId]` that display all reviews using the components from Lesson 1.3. Pre-render all pages at build time.

## Fast Track

1. Create `app/[productId]/page.tsx` with `params: Promise<{ productId: string }>` and `await params`
2. Add `generateStaticParams()` returning `products.map(p => ({ productId: p.slug }))`
3. Use `getProduct(productId)` with try/catch and `notFound()`, render `<Reviews product={product} />`

## Hands-on Exercise 1.4

Build dynamic product pages with static generation:

**Requirements:**

1. Create a dynamic route at `app/[productId]/page.tsx`
2. Fetch product data using `getProduct(slug)` from Lesson 1.2
3. Display product name, description, and reviews
4. Implement `generateStaticParams` to pre-render all 3 products
5. Handle 404s with Next.js `notFound()`

**Implementation hints:**

- Params are now a Promise in Next.js 15+ (use `await params`)
- `generateStaticParams` returns array of objects with route parameters
- Use the Reviews component from Lesson 1.3
- The page should be a Server Component (no "use client" needed)

## Understanding Dynamic Routes

**File structure:**

```
app/
├── page.tsx              # Homepage (/)
└── [productId]/
    └── page.tsx          # Dynamic route (/:productId)
```

**Routes created:**

- `/` → `app/page.tsx`
- `/mower` → `app/[productId]/page.tsx` (productId = "mower")
- `/ecoBright` → `app/[productId]/page.tsx` (productId = "ecoBright")
- `/aquaHeat` → `app/[productId]/page.tsx` (productId = "aquaHeat")

One file generates infinite routes.

## Step 1: Create Dynamic Route

Create `app/[productId]/page.tsx`:

```tsx title="app/[productId]/page.tsx"
import { notFound } from "next/navigation";
import { getProduct, getProducts } from "@/lib/sample-data";
import { Reviews } from "@/components/reviews";

export default async function ProductPage({
  params,
}: {
  params: Promise<{ productId: string }>;
}) {
  const { productId } = await params;

  let product;
  try {
    product = getProduct(productId);
  } catch {
    notFound();
  }

  return (
    <main className="min-h-screen p-8">
      <div className="max-w-4xl mx-auto space-y-8">
        {/* Product Header */}
        <div>
          <h1 className="text-4xl font-bold">{product.name}</h1>
          <p className="text-lg text-muted-foreground mt-2">
            {product.description}
          </p>
        </div>

        {/* Reviews */}
        <Reviews product={product} />
      </div>
    </main>
  );
}
```

**Key features:**

- `params` is a Promise (Next.js 15+ change)
- `await params` to get route parameters
- `try/catch` handles invalid product IDs
- `notFound()` renders 404 page

## Step 2: Add Static Generation

Add `generateStaticParams` to the same file:

```tsx title="app/[productId]/page.tsx" {34-40}
import { notFound } from "next/navigation";
import { getProduct, getProducts } from "@/lib/sample-data";
import { Reviews } from "@/components/reviews";

export default async function ProductPage({
  params,
}: {
  params: Promise<{ productId: string }>;
}) {
  const { productId } = await params;

  let product;
  try {
    product = getProduct(productId);
  } catch {
    notFound();
  }

  return (
    <main className="min-h-screen p-8">
      <div className="max-w-4xl mx-auto space-y-8">
        <div>
          <h1 className="text-4xl font-bold">{product.name}</h1>
          <p className="text-lg text-muted-foreground mt-2">
            {product.description}
          </p>
        </div>

        <Reviews product={product} />
      </div>
    </main>
  );
}

export function generateStaticParams() {
  const products = getProducts();

  return products.map((product) => ({
    productId: product.slug,
  }));
}
```

**What `generateStaticParams` does:**

- Runs at build time
- Returns array of route parameters to pre-render
- Next.js generates HTML for each route
- Pages load instantly (no server rendering needed)

## Step 3: Add Metadata

Add dynamic metadata for SEO:

```tsx title="app/[productId]/page.tsx" {1,42-61}
import { Metadata } from "next";
import { notFound } from "next/navigation";
import { getProduct, getProducts } from "@/lib/sample-data";
import { Reviews } from "@/components/reviews";

export default async function ProductPage({
  params,
}: {
  params: Promise<{ productId: string }>;
}) {
  const { productId } = await params;

  let product;
  try {
    product = getProduct(productId);
  } catch {
    notFound();
  }

  return (
    <main className="min-h-screen p-8">
      <div className="max-w-4xl mx-auto space-y-8">
        <div>
          <h1 className="text-4xl font-bold">{product.name}</h1>
          <p className="text-lg text-muted-foreground mt-2">
            {product.description}
          </p>
        </div>

        <Reviews product={product} />
      </div>
    </main>
  );
}

export function generateStaticParams() {
  const products = getProducts();

  return products.map((product) => ({
    productId: product.slug,
  }));
}

export async function generateMetadata({
  params,
}: {
  params: Promise<{ productId: string }>;
}): Promise<Metadata> {
  const { productId } = await params;

  let product;
  try {
    product = getProduct(productId);
  } catch {
    return {
      title: "Product Not Found",
    };
  }

  return {
    title: `${product.name} - Customer Reviews`,
    description: product.description,
  };
}
```

**Benefits:**

- Dynamic page titles (`<title>Mower3000 - Customer Reviews</title>`)
- SEO-friendly descriptions
- Falls back gracefully for 404s

## Try It

1. **Visit the homepage** at <http://localhost:3000>

2. **Click on a product card**

3. **You should see:**
   - Product name as heading
   - Product description
   - All reviews displayed with ratings, avatars, timestamps
   - Proper layout with separators

4. **Test all products:**
   - <http://localhost:3000/mower>
   - <http://localhost:3000/ecoBright>
   - <http://localhost:3000/aquaHeat>

5. **Test 404 handling:**
   - Visit <http://localhost:3000/invalid-product>
   - Should show Next.js 404 page

## Understanding Static Generation

**Build time:**

```bash
pnpm build
```

Output shows:

```
Route (app)
┌ ○ /
├ ○ /_not-found
└ ● /[productId]
  ├ /mower
  ├ /ecoBright
  └ /aquaHeat

○  (Static)  prerendered as static content
●  (SSG)     prerendered as static HTML (uses generateStaticParams)
```

The `●` symbol means "SSG" - statically generated using `generateStaticParams`. All product pages are pre-rendered at build time.

**Runtime:**
When a user visits `/mower`, Next.js serves pre-built HTML instantly. No database queries, no API calls, no computation.

## Static Generation Benefits

| Metric        | Dynamic (SSR)                | Static (SSG)        |
| ------------- | ---------------------------- | ------------------- |
| Server CPU    | High (renders every request) | Zero (pre-rendered) |
| Response Time | \~100-500ms                  | \~10ms              |
| Scalability   | Limited (server bottleneck)  | Infinite (CDN)      |
| Cost          | High (always computing)      | Low (compute once)  |

**Best for:**

- Product pages (content changes rarely)
- Blog posts
- Documentation
- Marketing pages

**Not ideal for:**

- User dashboards (personalized)
- Real-time data
- Frequently changing content

## Dynamic Route Patterns

**Single parameter:**

```
[productId] → /mower, /ecoBright, /aquaHeat
```

**Multiple parameters:**

```
[category]/[productId] → /electronics/mower, /appliances/aquaHeat
```

**Catch-all:**

```
[...slug] → /any/nested/path/works
```

**Optional catch-all:**

```
[[...slug]] → / and /any/path both work
```

## Extra Credit: Custom Not Found Page

Create `app/[productId]/not-found.tsx`.

```tsx title="app/[productId]/not-found.tsx"
import Link from "next/link";

export default function NotFound() {
  return (
    <main className="min-h-screen p-8">
      <div className="max-w-2xl mx-auto text-center space-y-4">
        <h1 className="text-4xl font-bold">Product Not Found</h1>
        <p className="text-muted-foreground">
          The product you're looking for doesn't exist.
        </p>
        <Link
          href="/"
          className="inline-block mt-4 px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
        >
          Back to Products
        </Link>
      </div>
    </main>
  );
}
```

Now invalid product URLs show a custom 404 with a link back home.

## Done-When

- [ ] Dynamic route created at `app/[productId]/page.tsx`
- [ ] Product pages display name, description, and reviews
- [ ] `generateStaticParams` pre-renders all 3 products
- [ ] 404 handling works for invalid product IDs
- [ ] Dynamic metadata sets page titles
- [ ] All product links from homepage work

## What's Next

Your app is functionally complete with product listings, individual product pages, and reviews display. In the next lesson, you'll deploy this to Vercel and see static generation in action on a production CDN.

***

**Sources:**

- [Next.js Dynamic Routes](https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes)
- [generateStaticParams](https://nextjs.org/docs/app/api-reference/functions/generate-static-params)
- [Static Site Generation](https://nextjs.org/docs/pages/building-your-application/rendering/static-site-generation)
- [notFound function](https://nextjs.org/docs/app/api-reference/functions/not-found)


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
