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!
Get notified when we release new tutorials, lessons, and other expert Nuxt content.
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.
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!
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.
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?
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>
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.