JSTAcademy
0 XP
Dashboard
Crash Courses
Dynamic Routes & Params
10 min
Masters+175 XP
Crash Courses · Masters

Dynamic Routes & Params

Build dynamic pages with route params, catch-all routes, and parallel routes.
10 min read+175 XP on completionCert: Next.js Crash Course
Tap any word in the text below to start reading from there.

Dynamic Routes & Params

Next.js routing handles flexible patterns for blogs, product pages, documentation, and complex UI patterns — all through folder naming conventions.

Dynamic Segment — [id]

// app/courses/[trackId]/[moduleId]/page.tsx
interface Props {
  params: Promise<{ trackId: string; moduleId: string }>
  searchParams: Promise<{ tab?: string }>
}

export default async function ModulePage({ params, searchParams }: Props) {
  const { trackId, moduleId } = await params
  const { tab = 'content' } = await searchParams

  const course = COURSES.find(c => c.id === moduleId && c.track === trackId)
  if (!course) return notFound()

  return <CourseViewer course={course} activeTab={tab} />
}

In Next.js 15, params and searchParams are Promises — always await them before accessing properties.

notFound() and generateMetadata

import { notFound } from 'next/navigation'
import type { Metadata } from 'next'

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { moduleId } = await params
  const course = COURSES.find(c => c.id === moduleId)

  if (!course) return { title: 'Not Found' }

  return {
    title: `${course.title} | JST Academy`,
    description: course.moduleObjective,
    openGraph: {
      title: course.title,
      description: course.moduleObjective,
    },
  }
}

Catch-All Routes

app/docs/[...slug]/page.tsx  → matches:
  /docs/intro
  /docs/api/courses
  /docs/api/courses/create

app/docs/[[...slug]]/page.tsx → also matches:
  /docs  (slug is undefined with optional catch-all)
// [slug] is an array of the path segments
export default async function DocsPage({
  params
}: {
  params: Promise<{ slug?: string[] }>
}) {
  const { slug = [] } = await params
  const path = slug.join('/')  // "api/courses/create"
  return <DocsContent path={path} />
}

Parallel Routes (Modal Pattern)

app/
  layout.tsx           ← receives { children, modal }
  page.tsx             ← the main page
  @modal/              ← parallel slot
    (.)courses/
      [id]/
        page.tsx       ← rendered as a modal overlay
  courses/
    [id]/
      page.tsx         ← full course page
// app/layout.tsx
export default function Layout({
  children,
  modal,
}: {
  children: React.ReactNode
  modal: React.ReactNode
}) {
  return (
    <>
      {children}
      {modal}  {/* modal renders on top of the main page */}
    </>
  )
}

This pattern powers Instagram-style route modals: clicking a photo opens it in a modal (intercepted route), but navigating to /photo/123 directly shows the full page.

URL Search Params

// Reading search params in a server component
export default async function CoursesPage({
  searchParams
}: {
  searchParams: Promise<{ track?: string; level?: string; page?: string }>
}) {
  const { track, level, page = '1' } = await searchParams

  const courses = await getCourses({ track, level, page: parseInt(page) })

  return (
    <div>
      <FilterBar currentTrack={track} currentLevel={level} />
      <CourseList courses={courses} />
      <Pagination currentPage={parseInt(page)} />
    </div>
  )
}
0%