Dynamic Routes


Defining routes by using predefined paths is not always enough for complex applications. In Next.js you can add brackets to a page ([param]) to create a dynamic route (a.k.a. url slugs, pretty urls, and others).

Consider the following page 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

Any route like /post/1, /post/abc, etc. will be matched by pages/post/[pid].js. The matched path parameter will be sent as a query parameter to the page, and it will be merged with the other query parameters.

For example, the route /post/abc will have the following query object:

{ "pid": "abc" }

Similarly, the route /post/abc?foo=bar will have the following query object:

{ "foo": "bar", "pid": "abc" }

However, route parameters will override query parameters with the same name. For example, the route /post/abc?pid=123 will have the following query object:

{ "pid": "abc" }

Multiple dynamic route segments work the same way. The page pages/post/[pid]/[comment].js will match the route /post/abc/a-comment and its query object will be:

{ "pid": "abc", "comment": "a-comment" }

Client-side navigations to a dynamic route can be handled with next/link.

Catch all routes


Dynamic routes can be extended to catch all paths by adding three dots (...) inside the brackets. For example:

  • pages/post/[...slug].js matches /post/a, but also /post/a/b, /post/a/b/c and so on.

Note: You can use names other than slug, such as: [...param]

Matched parameters will be sent as a query parameter (slug in the example) to the page, and it will always be an array, so, the path /post/a will have the following query object:

{ "slug": ["a"] }

And in the case of /post/a/b, and any other matching path, new parameters will be added to the array, like so:

{ "slug": ["a", "b"] }

A good example of catch all routes is the Next.js docs, a single page called pages/docs/[...slug].js takes care of all the docs you're currently looking at.

Optional catch all routes

Catch all routes can be made optional by including the parameter in double brackets ([[...slug]]).

For example, pages/post/[[...slug]].js will match /post, /post/a, /post/a/b, and so on.

The query objects are as follows:

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


  • Predefined routes take precedence over dynamic routes, and dynamic routes over catch all routes. Take a look at the following examples:

    • pages/post/create.js - Will match /post/create
    • pages/post/[pid].js - Will match /post/1, /post/abc, etc. But not /post/create
    • pages/post/[...slug].js - Will match /post/1/2, /post/a/b/c, etc. But not /post/create, /post/abc
  • Pages that are statically optimized by Automatic Static Optimization will be hydrated without their route parameters provided, i.e query will be an empty object ({}).

    After hydration, Next.js will trigger an update to your application to provide the route parameters in the query object.