Dynamic Routes

Pro Members Only

This lesson is available if you have an active subscription.

Alternatively, some member might be able to gift it to you.

If you already have a subscription, you can sign in.

Dynamic Routes

Subscription Required

You must have an active subscription to access this content.
If you already have a subscription, you can sign in.

Perhaps the most powerful feature of the NextJS router is its support for dynamic routes.

Simple Dynamic Segment

At its simplest, you can easily create a dynamic route that accepts a single parameter from the url.

To create a dynamic route we name the folder surrounding it with square brackets [].

In the below example we create a dynamic route for various names [name].

app/person/[name]/page.tsx
app/page.tsx
import { formatName } from '@/utils/formatName';

export default async function Page({
params,
}: {
params: Promise<{ name: string }>;
}) {
const { name } = await params;
return <div>Hi {formatName(name)}! 👋</div>;
}

Nested Dynamic Routes

A neat feature of dynamic segments is that you can even nest them, which allows you to accept multiple parameters.

This is demonstrated below where we accept two parameters [productId] and [reviewId].

app/product/[productId]/page.tsx
app/product/[productId]/review/[reviewId]/page.tsx
app/page.tsx
export default async function Page({
params,
}: {
params: Promise<{ productId: string }>;
}) {
const { productId } = await params;
return (
<div>
Product: <b>{productId}</b> 📦
</div>
);
}

Catch All Segment

You can even extend a dynamic route segment further by making it a catch all. This allows you to accept multiple url portions into a single parameter. You achieve this by adding ellipsis inside the brackets e.g. [...slug].

This is demonstrated in the below example for a shopfront that has a nested product hierarchy.

app/shop/[...slug]/page.tsx
app/page.tsx
export default async function Page({
params,
}: {
params: Promise<{ slug: string[] }>;
}) {
const { slug } = await params;
console.log(slug);
return (
<div>
Shopping for <b>{slug.join(" ► ")}</b> 🛍️
</div>
);
}

Optional Catch All Segment

A catch all segment can also be made optional allowing to capture the nested routes as well as the root route in a single page. We achieve this with double square brackets ([[]]).

An example is demonstrated below where we make the shopfront product hierarchy optional, which allows us to use the page to service the root /shop requests in addition to nested requests (e.g. /shop/clothes).

app/shop/[[...slug]]/page.tsx
app/page.tsx
export default async function Page({
params,
}: {
params: Promise<{ slug: undefined | string[] }>;
}) {
const { slug } = await params;
console.log("slug:", slug);

if (slug == null) {
return <div>Welcome to the Shop 🏪</div>;
}

return (
<div>
Shopping for <b>{slug.join(" ► ")}</b> 🛍️
</div>
);
}
javascript
typescript
react
playwright

Enjoy free content straight from your inbox 💌

No spam, unsubscribe at any time.

Transcript

00:00

Perhaps the most powerful feature of the next year's router is its support for dynamic routes. At its simplest, you can easily create a dynamic route that accepts a single parameter from the URL. Consider the simple use case of having dynamic URLs for different people within our organization. If you wanted to create these routes statically, we would have to create different pages for every single individual, and this is quickly going to get out of hand. The solution to this problem is creating a dynamic route, so we delete all these extra routes that we are creating and we create a single dynamic route.

00:34

And the way you create a dynamic route is by naming the folder surrounding it with squared brackets. The value that we provide within the squared brackets is going to become available as a parameter within our page component. Our page component accepts an object containing a property called pers, and PERS is going to be a promise containing the different parameters matched paradynamic route. Here we called our dynamic route name and therefore the PERS promise is going to have a property called name of type string. Since A URL only contains strings, the members

01:07

of PERS are always going to be string. The first thing that we are going to do within our server component is await the PERS promise and then destructure the name property. Now we can use this parameter however we want. For example, simply display it as a nicely formatted string with this code out of the way, let's demo it in action. If you visit slash person slash alpha, the per with name alpha is going to get passed in and we see a nice message I alpha. Similarly, the same thing happens for beta as well as for Charlie Chaplin. So dynamic routes allow us to handle multiple routes

01:41

with a single dynamic page. A neat feature of dynamic segments is that you can even nest them, which allows you to accept multiple parameters. As an example, considered an application that has different routes for various products, and additionally, the products individually can have sub routes for the different reviews. We already know how to build a dynamic route for the products. We create a dynamic route for the product page, giving it the parent product id, and here we are simply displaying the product ID within that page. For the reviews, we create a sub route called review,

02:15

and within that, create another dynamic segment and collect the review ID in a per called review id. The dynamic route will be passed in all the PERS that matched to arrive at that route. So just like the product page, we can accept the product id, but additionally we can also accept the review ID and we structured it from the paras just like we were structuring the product. And then we can use this review id, however we want, for example, simply displayed within the UI. With this code out of the way, let's take a look at it in action within the browser routes

02:48

that only match the product id. Take us to the product page, for example, product ball or product racket, and the product review pages take us to the nested dynamic route where We collect both the product and the review ID and display them to the user. You can even extend a dynamic route segment further by making it a catch. All this allows you to accept multiple URL portions into a single parameter. To demonstrate this, let's consider a real world use case where we have a shopfront that displays different products that exist within a hierarchy.

03:20

As an example, you might want to visit clothes or clothes stops or clothes stops t-shirt. Within our shopfront, we know that we can handle a single route segment by containing the folder name within square brackets. We can easily extend it to catch all nested route segments by simply adding triple dots before the folder name. If you've gone through a JavaScript course, you will recognize that this is actually inspired by array spread. To accept catchall pairs within our page, we simply modify the per type to be a string array instead of a simple string.

03:53

Let's just log out the slug pattern so that we can see this change in action. We can choose whatever we want with this array of slug paras. Let's just display it in a nice breadcrumb. With our code in place, let's jump into the browser to see our application in action. The catchall will match single pairs, for example, just the clothes and even multiple pairs. For example, clothes tops as well as clothes tops t-shirt. And you can see in the browser console that for catch-all segments, the matched pair is always going to be an array. A catch-all segment can also be made optional, allowing you

04:27

to capture the nested routes as well as the root route in a single page. Currently, a shopfront handles the routes where the different segments are passed in. For example, clothes, clothes stops, clothes stops, t-shirts. However, it might surprise you that it doesn't work when we visit the route shop, URL, and we can easily verify this within the browser, the different nested routes work. But the route shop route will result in a 4 0 4. One way to fix this is to turn the nested catchall route to become optional, to turn a dynamic route, to become optional.

05:00

Instead of single square brackets, we use double square brackets. Now within the page, instead of the pers always being passed in, we should expect the para to be undefined when it is matching the root route. Within the component code, we simply handle the case where the slug is going to be undefined and render out a root page. For example, a simple welcome message. Let's verify our change by taking a look at the application within the browser. Of course, the nested catchall routes continue to function as expected, but now it even functions for the root, in which case we can see on the console

05:33

that the slug is going to be undefined.