Dynamic Routes and Route Parameters

intermediate
11 min

Dynamic Routes and Route Parameters

Static folders cover pages you know in advance, but a blog with a thousand posts cannot have a thousand folders. Dynamic routes solve this: one folder pattern handles every post, product, or user profile. In this lesson you will build dynamic segments, read their parameters, pre-render them at build time, and handle missing content with 404s.

Creating a Dynamic Segment

Wrap a folder name in square brackets to make it dynamic:

text
app/blog/[slug]/page.js

Now /blog/hello-world, /blog/nextjs-tips, and any other /blog/anything all render this page. The matched value arrives in the params prop:

jsx
// app/blog/[slug]/page.js export default async function BlogPostPage({ params }) { const { slug } = await params; // params is a Promise in Next.js 15 const post = await getPost(slug); return ( <article> <h1>{post.title}</h1> <p>{post.body}</p> </article> ); }

Note the await: since Next.js 15, params is asynchronous. In Next.js 14 you could read params.slug directly; awaiting works in both mindsets going forward and is the convention to learn.

Handling Missing Content

When the slug does not match real content, show a 404 with notFound():

jsx
import { notFound } from 'next/navigation'; const post = await getPost(slug); if (!post) notFound();

This renders the nearest not-found.js file and sends a proper 404 status code - important for SEO, since you do not want search engines indexing empty pages.

Pre-Rendering with generateStaticParams

By default a dynamic route renders on demand. If you know the possible values ahead of time, generate them at build time for maximum speed:

jsx
// app/blog/[slug]/page.js export async function generateStaticParams() { const posts = await getAllPosts(); return posts.map((post) => ({ slug: post.slug })); }

At build time, Next.js calls this function, gets the list of slugs, and renders a static HTML page for each one. Visitors get CDN-fast pages, and slugs not in the list can still render on demand the first time they are requested.

Multiple Dynamic Segments

Segments compose naturally. An e-commerce route might look like:

text
app/shop/[category]/[productId]/page.js
jsx
export default async function ProductPage({ params }) { const { category, productId } = await params; // fetch product by category + id }

Catch-All Segments

Need to match an unknown number of segments, like documentation paths? Use a catch-all with three dots:

  • [...parts] matches /docs/a and /docs/a/b/c - params.parts is an array like ['a', 'b', 'c'].
  • [[...parts]] (double brackets) is optional: it also matches the bare /docs.
text
app/docs/[[...parts]]/page.js

This single file can power an entire documentation tree.

Reading Query Strings

URL query parameters like ?page=2 arrive through the separate searchParams prop on pages:

jsx
export default async function BlogIndex({ searchParams }) { const { page = '1' } = await searchParams; const posts = await getPosts({ page: Number(page) }); // ... }

Using searchParams opts the page into dynamic rendering, since the server cannot know query values at build time.

Key Takeaways

  • [folder] creates a dynamic segment; the value arrives via the awaited params prop.
  • notFound() returns a real 404 for slugs with no matching content.
  • generateStaticParams pre-renders known paths at build time for CDN speed.
  • Catch-all [...slug] and optional [[...slug]] segments match arbitrary depth.
  • searchParams reads query strings and makes the page dynamic.

Next up: Lesson 9 leaves pages behind and builds backend API endpoints with route handlers.

Dynamic Routes and Route Parameters - Next.js | CodeYourCraft | CodeYourCraft