Skip to the solution
MediumFrontend8 min read

What are dynamic routes and catch-all segments in Next.js?

925 views

The problem

Question presented to candidate: "We have thousands of blog posts and a product catalogue with nested categories. How do you model those URLs in the App Router, and what exactly does the page receive?"

What a strong answer should cover:

  • A folder named [slug] is a dynamic segment: it matches one URL segment and passes it as params.slug (a string).
  • [...slug] is a catch-all: one or more segments, as a string array; the bare parent URL does not match.
  • [[...slug]] is an optional catch-all: zero or more segments, so the parent URL also matches and the value is undefined.
  • params is a Promise since Next.js 15: await params in Server Components, use(params) or useParams() in Client Components; type it with PageProps<'/route'>.
  • generateStaticParams prerenders known values at build time; dynamicParams = false turns unknown values into a 404.
  • Values arrive URL-encoded and unvalidated: decode and validate them, and call notFound() when nothing matches.

Clarifying questions expected:

  • "Are the slugs known at build time?" β€” decides between prerendering with generateStaticParams and rendering on demand.
  • "Should an unknown slug 404 or be generated on first request?" β€” that is the dynamicParams switch.

Code / implementation expected: Yes β€” [slug], [...categories] and [[...path]] pages reading params.

  • nextjs
  • dynamic-routes
  • routing
  • params

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

8 min read Β· 8 sections

Read the code

Dynamic, catch-all and optional catch-all pages
// app/lib/posts.ts
const posts: Record<string, { title: string }> = {
  hello: { title: "Hello" },
  world: { title: "World" },
};
export async function getPost(slug: string) {
  return posts[slug];
}
export async function getAllSlugs() {
  return Object.keys(posts);
}

// app/blog/[slug]/page.tsx
import { notFound } from "next/navigation";
import { getAllSlugs, getPost } from "../../lib/posts";

// Prerender the known posts at build time. Unknown slugs still render on demand,
// unless you add: export const dynamicParams = false (then they return 404).
export async function generateStaticParams() {
  return (await getAllSlugs()).map((slug) => ({ slug }));
}

export default async function PostPage({ params }: PageProps<"/blog/[slug]">) {
  const { slug } = await params; // params is a Promise since Next.js 15
  const post = await getPost(decodeURIComponent(slug)); // values arrive URL-encoded
  if (!post) notFound();
  return <h1>{post.title}</h1>;
}

// app/shop/[...categories]/page.tsx
// /shop/a        -> { categories: ["a"] }
// /shop/a/b/c    -> { categories: ["a", "b", "c"] }
// /shop          -> 404 (a catch-all needs at least one segment)
export default async function ShopPage({ params }: PageProps<"/shop/[...categories]">) {
  const { categories } = await params;
  return <p>Breadcrumb: {categories.join(" / ")}</p>;
}

// app/docs/[[...path]]/page.tsx
// /docs          -> { path: undefined }
// /docs/x/y      -> { path: ["x", "y"] }
export default async function DocsPage({ params }: PageProps<"/docs/[[...path]]">) {
  const { path = [] } = await params;
  return <p>{path.length ? `Doc: ${path.join("/")}` : "Docs home"}</p>;
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 38 of 129 in Next.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Next.js questions
Up nextMedium

How do you analyze and reduce bundle size in Next.js?

Continue