Dynamic Route Matching with Params
Very often we will need to map routes with the given pattern to the same component. For example, we may have a User component which should be rendered for all users but with different user IDs. In Vue Router we can use a dynamic segment in the path to achieve that, we call that a param:
js
import User from './User.vue'
// these are passed to `createRouter`
const routes = [
// dynamic segments start with a colon
{ path: '/users/:id', component: User },
]Now URLs like /users/johnny and /users/jolyne will both map to the same route.
A param is denoted by a colon :. When a route is matched, the value of its params will be exposed as route.params in every component. Therefore, we can render the current user ID by updating User's template to this:
vue
<template>
<div>
<!-- The current route is accessible as $route in the template -->
User {{ $route.params.id }}
</div>
</template>You can have multiple params in the same route, and they will map to corresponding fields on route.params. Examples:
| pattern | matched path | route.params |
|---|---|---|
| /users/:username | /users/eduardo | { username: 'eduardo' } |
| /users/:username/posts/:postId | /users/eduardo/posts/123 | { username: 'eduardo', postId: '123' } |
In addition to route.params, the route object also exposes other useful information such as route.query (if there is a query in the URL), route.hash, etc. You can check out the full details in the API Reference.
Reacting to Params Changes
One thing to note when using routes with params is that when the user navigates from /users/johnny to /users/jolyne, the same component instance will be reused. Since both routes render the same component, this is more efficient than destroying the old instance and then creating a new one. However, this also means that some lifecycle hooks of the component will not be called.
To react to params changes in the same component, you can simply watch anything on the route object, in this scenario, the route.params:
vue
<script setup>
import { watch } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()
watch(
() => route.params.id,
(newId, oldId) => {
// react to route changes...
}
)
</script>vue
<script>
export default {
created() {
this.$watch(
() => this.$route.params.id,
(newId, oldId) => {
// react to route changes...
}
)
},
}
</script>Or, use the beforeRouteUpdate navigation guard, which also allows you to cancel the navigation:
vue
<script setup>
import { onBeforeRouteUpdate } from 'vue-router'
// ...
onBeforeRouteUpdate(async (to, from) => {
// react to route changes...
userData.value = await fetchUser(to.params.id)
})
</script>vue
<script>
export default {
async beforeRouteUpdate(to, from) {
// react to route changes...
this.userData = await fetchUser(to.params.id)
},
// ...
}
</script>Catch all / 404 Not found Route
Regular params will only match characters in between URL fragments, separated by /. If we want to match anything, including slashes, we can use a custom param regexp by adding the regexp inside parentheses right after the param:
js
const routes = [
// will match any path and put it under `route.params.pathMatch`
{ path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound },
// will match any path starting with `/user-` and put the rest under `route.params.afterUser`
{ path: '/user-:afterUser(.*)', component: UserGeneric },
]The pathMatch name is a convention. You can choose any descriptive param name, such as catchAll.
The (.*) custom regexp allows the param to match slashes. The trailing * is a repeatable param modifier, not part of the regexp. It makes the router store the matched path as an array of segments when it matches one or more segments, or as an empty string when it matches the root (/). The router then builds the URL from those segments when using a named route. Without the modifier, the param is a single string and its slashes are percent-encoded.
This matters when navigating to a named catch-all route from the current path. Split the path into segments instead of passing it as one string:
js
router.push({
name: 'NotFound',
// preserve current path and remove the first char to avoid the target URL starting with `//`
params: { pathMatch: route.path.substring(1).split('/') },
// preserve existing query and hash if any
query: route.query,
hash: route.hash,
})If you navigate with a path string instead, for example router.push('/not/found'), the repeatable modifier is not needed for this purpose.
If you are using History mode, make sure to follow the instructions to correctly configure your server as well.
Advanced Matching Patterns
Vue Router uses its own path matching syntax, inspired by the one used by express, so it supports many advanced matching patterns such as optional params, zero or more / one or more requirements, and even custom regex patterns. Please check the Advanced Matching documentation to explore them.
