Dynamic Pages and Route Params in Nuxt 3

Nuxt 3 routing builds on the functionality of Nuxt 2 by giving you more ways than ever before to express your dynamic routes. Get more information on new features as well as what got dropped!

Josh Deltener
Nuxt 3

Mastering Nuxt 3 course is here!

Get notified when we release new tutorials, lessons, and other expert Nuxt content.

Click here to view the Nuxt 3 course

Have you ever needed to represent a set of pages without knowing what their urls are at build time?

These are called dynamic pages and use route params (which are different from query params).

To create a dynamic page in Nuxt 3 we use the useRoute composable in our script and the special $route object in our template, like this:

File: /pages/users/[id].vue
Url:  /users/123

<template>
  <span>User ID: {{ $route.params.id }}</span>
</template>

<script setup>
const route = useRoute()
console.log(route.params) // { id: '123' }
</script>

But there’s a whole lot more to this, so let’s dig deeper.

Dynamic Routes in Nuxt 3

Not only can we do dynamic segments in our routes, but we can have as many nested dynamic segments as we want:

File: /pages/[people]/[id].vue
Url:  /developer/123

const route = useRoute()
console.log(route.params) // { people: 'developer', id: '123' }

Now let's start to get a little crazy. Using brackets allows you to also match both static and dynamic text within a route!

File: /pages/person-[name]/index.vue
Url:  /person-spongebob/

const route = useRoute()
console.log(route.params) // { name: 'spongebob' }

Keep in mind, when you mix dynamic and static segments like this, you’ll need to use a - hyphen separator or some other unique character. This will help provide a boundary between the static and dynamic segments.

You can even do partial matches on child-routes as well:

File: /pages/person-[name]/lives-[area].vue
Url:  /person-spongebob/lives-ocean/

const route = useRoute()
console.log(route.params) // { name: 'spongebob', lives: 'ocean'}

You can also add in optional dynamic segments to your route using double square brackets [[...]]:

File: /pages/person-[name]/lives-[area]/[[color]].vue
Url:  /person-spongebob/lives-ocean/yellow

const route = useRoute()
console.log(route.params)
// {
//   name: 'spongebob',
//   lives: 'ocean',
//   color: 'yellow'
// }

The difference with the optional segment is that it will match URLs that have it as well as URLs that don’t have it. So we if don’t provide a colour it will still match:

File: /pages/person-[name]/lives-[area]/[[color]].vue
Url:  /person-spongebob/lives-ocean

const route = useRoute()
console.log(route.params)
// {
//   name: 'spongebob',
//   lives: 'ocean',
//   color: undefined
// }

Nuxt 3 even allows you to do multiple dynamic matching! The ways in which you can combine these operators is literally limitless:

File: /pages/person-[name]-[color]/lives-[area]-[city].vue
Url:  /person-spongebob-yellow/lives-ocean-bikinibottom/

const route = useRoute()
console.log(route.params)
// { 
//   name: 'spongebob', 
//   color: 'yellow', 
//   area: 'ocean', 
//   city: 'bikinibottom'
// }

There is one more surprise, though.

Nuxt 3 also lets you have catchall files that can match any number of arguments. Anything that matches after the folder name will be added to an array using the key name of your filename:

File: /pages/person/[...slug].vue

Url:  /person/spongebob/123/abc
const route = useRoute()
console.log(route.params) // { slug: ['spongebob', '123', 'abc' ] }

Url: /person/
const route = useRoute()
console.log(route.params) // {}

As you can see, the Nuxt 3 routing matcher is very powerful!

Accessing Route Parameters in the Template

In Nuxt 3 we can access any route parameter inside of our template using the special $route object:

<template>
  <span>Name: {{ $route.params.name }}</span>
</template>

Anything you can access on the route object can be accessed this way in the template.

Dynamic Server Routes

When building an API endpoint (aka a server route), we very often need to use dynamic routes.

Making this happen is a little different, since we’re no longer in Vue-land but in Nitro-land:

File: /pages/[people]/[id].vue
Url:  /developer/123

export default defineEventHandler((event) => {
  const { people, id } = getRouterParams(event)
});

If you want just a specific route parameter, you can use getRouterParam instead:

File: /pages/[people]/[id].vue
Url:  /developer/123

export default defineEventHandler((event) => {
  const people = getRouterParam(event, 'people')
});

The more common approach is to grab these parameters directly from the event object, but I wouldn’t recommend it:

File: /pages/[people]/[id].vue
Url:  /developer/123

export default defineEventHandler((event) => {
  const { people, id } = event.context.params
});

This is because you’re coupling your code with the specific structure of the event object. If the object changes in the future, all your code will break.

However, by using the getRouterParam and getRouterParams methods, you’ll be safe. These methods will be updated alongside any other changes, keeping all those details internal and encapsulated. Do you really care what’s inside the event object?

How to get the route name

If you need the name of the route for your app, just access it off of the route object.

In your script:

const route = useRoute();
const routeName = route.name;

In the template:

<template>
  <span>Route name: {{ $route.name }}</span>
</template>

Dynamic Routes in Nuxt 2

With Nuxt 2 dynamic routes are handled by using an underscore in front of a file or folder.

File: /pages/users/_id.vue
Url:  /users/123

$route.params = { id: "123" }

You can even use both a dynamic folder and a file.

Josh Deltener
Josh is a true Nuxt Master. He has over 30 years of development experience and currently works as director of front-end technology at RealTruck. He is also a Nuxt ambassador and contributor.

Follow MasteringNuxt on