Lesson 2 of 81
You can't hand-write a page.tsx for every one of ten thousand product IDs. Dynamic segments let one file generate all ten thousand routes — and in current Next.js, reading which one matched means awaiting a promise, not reading a prop.
11 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
Question 1 of 3
A route is defined as `app/shop/[...slug]/page.tsx`. A user visits `/shop` with no further path segments. What happens?
Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
A dynamic segment is a folder name wrapped in square brackets that acts as a placeholder in the URL instead of a literal path piece. Next.js matches whatever the visitor actually typed into that slot and hands it to your component as a value called params.
There are three flavors, each capturing a different shape of URL:
[slug] — captures exactly one segment. app/blog/[slug]/page.tsx matches /blog/hello-world and gives you { slug: "hello-world" }.[...slug] (catch-all) — captures one or more remaining segments as an array. app/shop/[...slug]/page.tsx matches /shop/a, /shop/a/b, and /shop/a/b/c, giving { slug: ["a", "b"] } for the second case. It does not match bare /shop.[[...slug]] (optional catch-all) — the same as catch-all, but the extra brackets make the parameter itself optional, so it also matches the bare route with no trailing segments at all, giving { slug: undefined }.// app/blog/[slug]/page.tsx
export default async function BlogPostPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await getPost(slug);
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
The detail that trips people up coming from older Next.js tutorials: params is a Promise, not a plain object. You have to await it (or, in a Client Component, unwrap it with React's use()) before you can read slug off of it — destructuring params.slug directly no longer works, because params isn't the object itself, it's a promise that eventually resolves to that object. This changed because Next.js needs the flexibility to resolve dynamic params asynchronously, as part of streaming and partial prerendering the surrounding page while the specific param-driven content is still being determined.
params must be awaited — it's a Promise in current Next.js, not a synchronous object. Older code (pre-v15) treated params.slug as directly readable; that pattern is deprecated and will eventually stop working.[...slug] will never match the route with zero extra segments. /shop alone does not match app/shop/[...slug]/page.tsx — you'd get a 404. Only the optional catch-all ([[...slug]]) matches the bare route too.string, string[], or string[] | undefined), never a narrow union, because the URL bar accepts arbitrary text from any visitor — narrowing to a known set of valid values is something you validate yourself at runtime, not something the type system gives you for free.await params since Client Components can't be async functions — instead you unwrap the promise with React's use(params) hook."How would you build a route that handles both /docs and /docs/getting-started/installation, from one file?" tests whether a candidate knows optional catch-all segments exist at all, and separately, whether they know params is asynchronous in current Next.js — a detail that silently breaks a lot of code copied from older tutorials.