Vue Tip: Re-Rendering Vue Routes When Path Parameters Change
Michael Hoffmann
@mokkapps
In Vue applications, we use Vue Router to define routes and map URLs to components. These components are rendered and updated whenever the URL changes.
A typical use case is a dynamic route pattern with params: /users/:id
. It allows you to specify a parameter in the URL. The :id
in this route pattern is a placeholder for a dynamic value, which can change based on the user's actions. Typically you will trigger a backend endpoint with the given id
to fetch user details.
Let's take a look at an example:
The demo code of this article is interactively available in the following StackBlitz project:
<script setup>
import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import UserId from '@/components/UserId.vue'
import { getUserDetails } from '../api/userApi.js'
const route = useRoute()
const router = useRouter()
const loading = ref(false)
const details = ref(null)
async function fetchDetails(id) {
loading.value = true
try {
details.value = await getUserDetails(id)
} catch (error) {
console.error('Failed to fetch details', error)
details.value = 'Error'
} finally {
loading.value = false
}
}
function navigateToNextUser() {
const nextId = Number(route.params.id) + 1
router.push({ name: 'UserDetails', params: { id: nextId } })
}
onMounted(async () => {
console.log('UserDetails mounted')
fetchDetails(route.params.id)
})
</script>
<template>
<h2>User Details</h2>
<div>
<span>ID: {{ route.params.id }}</span>
<span v-if="loading">Loading details ...</span>
<span v-else>Details: {{ details }}</span>
<button @click="navigateToNextUser">Next User</button>
</div>
</template>
This component uses the onMounted
lifecycle hook to fetch user details when the component is mounted. The user's ID is read via route.params.id
from the useRoute
composable.
So far, everything works fine and the user details are visible and they get updated if we update the route path manually by changing /users/1
to /users/2
.
But our component contains a button that triggers a programmatic route update using router.push
.
Programmatically altering the path does not trigger the re-rendering of the view. As a result, the mounted()
hooks do not fire, and the nested components do not reload, leading to unexpected behavior.
Solution 1: Watcher
The simplest solution is to add a watcher that fetches the details if the route params have changed:
<script setup>
// ...
watch(
() => route.params.id,
(newId, oldId) => {
fetchDetails(route.params.id)
}
)
</script>
Solution 2: Route Guard
Another solution is the beforeRouteUpdate route guard:
<script setup>
// ...
onBeforeRouteUpdate(async (to, from) => {
if (to.params.id !== from.params.id) {
fetchDetails(route.params.id)
}
})
</script>
Of course, you can also use the route guard with Options API.
It adds a navigation guard that triggers whenever the current location is about to be updated. Similar to beforeRouteUpdate
but can be used in any component. The guard is removed when the component is unmounted.
Difference Between Solutions 1 & 2
There is a big difference between using watch
and beforeRouteUpdate
:
The route guard is called before the value of the route
object actually changes. The watcher is called after the value of route
has changed.
Using the beforeRouteUpdate
navigation guard, you can determine whether or not you want to prevent the route from changing or go to a different route entirely.
If you liked this Vue tip, follow me on X to get notified about new tips, blog posts, and more. Alternatively (or additionally), you can subscribe to my weekly Vue & Nuxt newsletter :
Nuxt Tip: Use DevTools to Know Your App Better
Vue Tip: Destructure Props in Composition API Without Losing Reactivity