Free tutorials & notes in Hindi & English · Clean code examples 🧰 Free PDF & Image Tools →
🔵 App Router – Pages, Layouts & Navigation  ·  Lesson 12

Dynamic Routes ([slug])

The problem: 100 courses, 100 files?

Your site has many courses, blog posts or products. Creating app/courses/c-programming/page.jsx, app/courses/python-ai/page.jsx… by hand is impossible. A dynamic route uses square brackets in the folder name so one file handles every URL.

📁 app/
  📁 courses/
    page.jsx            # /courses           (list)
    📁 [slug]/
      page.jsx          # /courses/c-programming
                        # /courses/python-ai
                        # /courses/nextjs      ... any slug
One [slug] folder serves every course URL

Creating app/courses/[slug]/page.jsx

JS – lib/courses.js
// lib/courses.js  (short version of the EduSite data file)
export const courses = [
  { slug: 'c-programming', title: 'C Programming', level: 'Beginner', fee: 2499 },
  { slug: 'python-ai',     title: 'Python + AI',   level: 'Beginner', fee: 3499 },
  { slug: 'nextjs',        title: 'Next.js',       level: 'Intermediate', fee: 4499 },
]

export function getCourse(slug) {
  return courses.find((c) => c.slug === slug)
}
JSX – app/courses/[slug]/page.jsx
// app/courses/[slug]/page.jsx
import { notFound } from 'next/navigation'
import { getCourse } from '@/lib/courses'

export default async function CoursePage({ params }) {
  const { slug } = await params          // "nextjs" for /courses/nextjs
  const course = getCourse(slug)
  if (!course) notFound()                // real 404 for /courses/abc

  return (
    <main>
      <span className="badge">{course.level}</span>
      <h1>{course.title}</h1>
      <p>Fee: ₹{course.fee}</p>
    </main>
  )
}
🔒 localhost:3000/courses/nextjsEduSiteBlogCoursesHomeIntermediateNext.jsFee: ₹4499
Next.js course detail page from a dynamic route
Real EduSite page rendered by app/courses/[slug]/page.jsx

How a URL becomes params

/courses/nextjsURL[slug] matchslug = "nextjs"await params{ slug }getCourse()find dataHTMLpage
The folder name in brackets becomes the key in params

Unknown slug → notFound()

Visiting /courses/abc should not show an empty page — Google would treat it as a "soft 404". notFound() renders app/not-found.jsx with a real 404 status code.

generateStaticParams — pre-build the pages

JSX – generateStaticParams
// app/courses/[slug]/page.jsx  (add above the component)
import { courses } from '@/lib/courses'

export function generateStaticParams() {
  return courses.map((c) => ({ slug: c.slug }))
  // → [{ slug: 'c-programming' }, { slug: 'python-ai' }, { slug: 'nextjs' }]
}

// Optional: return 404 for any slug NOT in the list (no on-demand render)
export const dynamicParams = false
Output of npm run build
Route (app) ┌ ○ / ├ ○ /courses ├ /courses/[slug] │ ├ ● /courses/c-programming │ ├ ● /courses/python-ai │ └ ● /courses/nextjs ○ (Static) prerendered as static content ● (SSG) prerendered as static HTML (uses generateStaticParams)

Without generateStaticParams

  • Page rendered on first request
  • Works for any new slug
  • Slightly slower first visit

With generateStaticParams

  • HTML built during npm run build
  • Served instantly from cache/CDN
  • Best for SEO pages
On-demand vs pre-built

Catch-all routes [...slug]

JSX – catch-all routes
// app/docs/[...slug]/page.jsx
export default async function Docs({ params }) {
  const { slug } = await params
  // /docs/react          → slug = ['react']
  // /docs/react/hooks    → slug = ['react', 'hooks']
  return <h1>Docs: {slug.join(' / ')}</h1>
}

// app/shop/[[...filters]]/page.jsx  (optional catch-all)
// /shop             → filters = undefined
// /shop/men/shoes   → filters = ['men', 'shoes']
FolderURLparams
[slug]/blog/hello{ slug: 'hello' }
[...slug]/docs/a/b{ slug: ['a','b'] }
[[...slug]]/shop{ slug: undefined }
[cat]/[id]/mobile/42{ cat: 'mobile', id: '42' }

The part after ? is not a route — it arrives in the page as searchParams (also a Promise).

JSX – app/courses/page.jsx
// app/courses/page.jsx  →  /courses?q=python&level=beginner
import Link from 'next/link'
import { searchCourses } from '@/lib/courses'

export default async function Courses({ searchParams }) {
  const { q = '' } = await searchParams
  const list = searchCourses(q)
  return (
    <main>
      <h1>{q ? `Results for "${q}"` : 'All courses'}</h1>
      <p>{list.length} course(s) found</p>
      {list.map((c) => (
        <p key={c.slug}><Link href={`/courses/${c.slug}`}>{c.title}</Link></p>
      ))}
    </main>
  )
}

Common mistakes

  • params.slug without await → in Next.js 16 it is undefined/error. Use const { slug } = await params.
  • Folder named (slug) or {slug} → must be square brackets [slug].
  • Returning numbers in generateStaticParams ({ id: 5 }) → values must be strings ('5').
  • Rendering "Not found" text with status 200 → use notFound().

Summary

  • [slug] folder → one file for many URLs; read with await params.
  • notFound() gives a real 404; generateStaticParams pre-builds pages.
  • [...slug] for nested paths, searchParams for ?query values.
✅ Practice

Create app/blog/[slug]/page.jsx with 3 posts in an array. Add generateStaticParams and check the build output shows ● for the 3 posts.

Frequently Asked Questions

Why do I need await params in Next.js 16?
Since Next.js 15, params and searchParams are Promises so that Next.js can start rendering before they are ready. In Next.js 16 synchronous access is removed, so always write const { slug } = await params.
What is the difference between [slug] and [...slug]?
[slug] matches exactly one segment (/blog/hello). [...slug] matches one or more segments (/docs/a/b/c) and gives an array. [[...slug]] also matches the folder root with no segment.
Is generateStaticParams required?
No. Without it pages are rendered on demand. With it, the listed pages are built as static HTML at build time, which is faster and great for SEO.
How do I show a 404 for a wrong slug?
Call notFound() from next/navigation when your data lookup returns nothing. Next.js renders not-found.jsx with a real 404 status.
← Next.js Tutorial
🔗

Share this topic with a friend

यह topic किसी दोस्त को भेजें

Found it useful? Send it to a classmate learning the same thing.

अच्छा लगा? जो दोस्त यही सीख रहा है, उसे भेज दीजिए।

Next topicRoute Groups & Private Folders Your progress: 12 / 37 (32%) · Lesson 13 / 37 Continue →

How was this lesson?

Your feedback helps us make the content better.

Write a review or suggestion (optional)

After approval, your name and city may appear publicly with your review. Please do not write your email or phone number.

▶ CodingEasily on YouTube

Learn with video lessons in easy Hindi

Featured video: RDBMS Part 3 – Class XI Commerce IT

▶ Subscribe – it’s free