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>
)
}Reading progress
0% read
In this module
[id]
[...slug]
[[...slug]]
Parallel Routes
0%