Lesson Async server components answered the data-fetching question, and the shape of that answer matters for dynamic routes.
A server component waits for data by being declared async and awaiting the fetch directly in its body. Server components are allowed to be async functions, so they simply await a fetch call or a database query inline, with no hooks involved. The useEffect dance from the React course is only needed in client components, which cannot be async.
Dynamic routes lean on this heavily, because the thing a dynamic page awaits first is the URL itself.
One file, a thousand pages
A blog has one post layout but thousands of post URLs. Creating a folder per post is obviously impossible. Instead you create one dynamic segment by wrapping a folder name in square brackets:
app/blog/[slug]/page.js
This single file serves /blog/hello-world, /blog/why-frameworks-win, and every other /blog/<anything> URL. The changing part is handed to your component as the params prop.
This should feel familiar. In Backend with Node.js you wrote app.get("/posts/:id") and read req.params.id. It is the same idea, spelled with brackets instead of a colon, and derived from the file system instead of a registration call.
Reading params (await it!)
// app/blog/[slug]/page.js export default async function PostPage({ params }) { const { slug } = await params; const res = await fetch(`https://api.example.com/posts/${slug}`); const post = await res.json(); return <h1>{post.title}</h1>; }
Decode it:
- In current Next.js,
paramsis a Promise, so you mustawaitit before destructuring. Older tutorials show plainparams.slug, which is the outdated API. - Visiting
/blog/hello-worldgivesslug === "hello-world", which the component uses to fetch exactly that post. - One file, one fetch, infinitely many URLs.
What params resolves to
With app/shop/[productId]/page.js deployed, a user visits /shop/42. Awaiting params gives you { productId: "42" }.
Two details in that small object are worth spelling out:
- The key matches the bracket folder name exactly. The folder is
[productId], so the key isproductId. Rename the folder and the key changes with it. - The value is always a string, taken verbatim from the URL. Even though
42looks like a number, URLs carry text, so you get"42". Convert it yourself withNumber(productId)when you need arithmetic.
slugify: making titles URL-safe
Blog posts need URL-safe slugs like the ones that fill a [slug] route. slugify(title) builds one: it lowercases the title, removes every character that is not a lowercase letter, digit, or space, trims the ends, and turns each run of spaces into a single hyphen. The regex work here is the same kind you met in Advanced JavaScript.
function slugify(title) { return title .toLowerCase() .replace(/[^a-z0-9 ]/g, "") .trim() .replace(/ +/g, "-"); } console.log(slugify("Hello, Next.js!")); console.log(slugify(" Server Components 101 ")); console.log(slugify("10 Tips & Tricks"));
Output
hello-nextjs server-components-101 10-tips-tricks
Why the order of steps matters
- Lowercasing happens first so the character filter only has to allow
a-z, not both cases. .replace(/[^a-z0-9 ]/g, "")deletes punctuation such as the comma, the period inNext.js, and the ampersand. Note that deleting&fromTips & Tricksleaves a double space behind..trim()removes the leading and trailing whitespace that would otherwise become stray hyphens.- The
+in/ +/gcollapses runs of spaces into one hyphen, which cleans up exactly the double space the ampersand removal created.