Dynamic Routes

Although it is very nice that Next.js defines routes by using predefined paths, it is not always enough for more complex applications.

A way to create more complex routes is by adding brackets to the route, this will turn the route into a dynamic route and adds a bunch of functionality.

There are a few ways to make routing a more complex project.

Adding [ ] to a route

Let's look at the little bit of example code below.

This is a dynamic routing file.

// file: pages/post/[pid].js

import { useRouter } from 'next/router'
const Post = () =>{
  const router = useRouter()
  const { pid } = router.query
    return <p>Post: {pid}</p>
}

export default Post

From the above code, there are different routes and queries that can work.

  • Route: /post/abc

    • Has this query: { "pid": "abc" }

  • Route: /post/abc?foo=bar

    • Has this query: { "foo": "bar", "pid": "abc" }

Important is to know that route parameters will override query parameters if they have the same name.

  • Route: /post/abc?pid=123

    • Has this query: { "pid": "abc" }

The above also works when you have multiple dynamic route segments.

  • Page: pages/post/[pid]/[comment].js

    • Has this route: /post/abc/a-comment

    • Has this query: { "pid": "abc", "comment": "a-comment" }

When working with client-side navigation to dynamic routes, this is handled with next/link.

Above routes turned in to links

<ul>
  <li>
    <Link href="/post/abc">
      <a>Go to pages/post/[pid].js</a>
    </Link>
  </li>
  <li>
    <Link href="/post/abc?foo=bar">
      <a>Also goes to pages/post/[pid].js</a>
    </Link>
  </li>
  <li>
    <Link href="/post/abc/a-comment">
      <a>Go to pages/post/[pid]/[comment].js</a>
    </Link>
  </li>
</ul>

Catching all routes

When using dynamic routes, you can extend to catch all paths by adding three dots inside the backets:

  • pages/post/[...slug].js matches all of the following routes:

    • /post/a

    • /post/a/b

    • /post/a/b/c

How does this work?

When you use a matched parameter, it will be sent as a query parameter to the page. But, it will also be an array. So the query object looks different.

  • For the route /post/a, the query object will be { "slug": ["a"] }.

  • And for the route /post/a/b/c, the query object will be { "slug": ["a", "b", "c"] }.

Using optional catch all routes

You can create optional catch all routes by including the parameter inside of double brackets like [[...slug]].

  • The page will be pages/post/[[..slug]].js and the routes can be /post, /post/a and /post/a/b.

    • The difference between catch all and optional catch all routes is that with optional catch all routes the route without the parameter is also matched.

  • Query objects would be like this:

{ } // GET `/post` which is an empty object 
{ "slug": ["a"] } // `GET /post/a` (single-element array)
{ "slug": ["a", "b"] } // `GET /post/a/b` (multi-element array)

Caveats

When working with dynamic routing, there are two rules that are very important.

  1. Predefined routes take precedence over dynamic routes.

  2. Dynamic routes take precedence over catch all routes.

The above rules would lead to the following:

  • pages/post/create.js

    • Will match /post/create

  • pages/post/[pid].js

    • Will match /post/1.

    • Will match /post/abc.

    • Will not match /post/create.

    • Will not match /post/abc.