Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Vue Router 4 reference covering navigation guards, route params, lifecycle interactions, and common gotchas.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
reference/router-param-change-no-lifecycle.md
1---2title: Route Param Changes Do Not Trigger Lifecycle Hooks3impact: HIGH4impactDescription: Navigating between routes with different params reuses the component instance, skipping created/mounted hooks and leaving stale data5type: gotcha6tags: [vue3, vue-router, lifecycle, params, reactivity]7---89# Route Param Changes Do Not Trigger Lifecycle Hooks1011**Impact: HIGH** - When navigating between routes that use the same component (e.g., `/users/1` to `/users/2`), Vue Router reuses the existing component instance for performance. This means `onMounted`, `created`, and other lifecycle hooks do NOT fire, leaving you with stale data from the previous route.1213## Task Checklist1415- [ ] Use `watch` on route params for data fetching16- [ ] Or use `onBeforeRouteUpdate` in-component guard17- [ ] Or use `:key="route.params.id"` to force re-creation (less efficient)18- [ ] Never rely solely on `onMounted` for route-param-dependent data1920## The Problem2122```vue23<!-- UserProfile.vue - Used for /users/:id -->24<script setup>25import { ref, onMounted } from 'vue'26import { useRoute } from 'vue-router'2728const route = useRoute()29const user = ref(null)3031// BUG: Only runs once when component first mounts!32// Navigating from /users/1 to /users/2 does NOT trigger this33onMounted(async () => {34user.value = await fetchUser(route.params.id)35})36</script>3738<template>39<div>40<!-- Still shows User 1 data when navigating to /users/2! -->41<h1>{{ user?.name }}</h1>42</div>43</template>44```4546**Scenario:**471. Visit `/users/1` - Component mounts, fetches User 1 data482. Navigate to `/users/2` - Component is REUSED, onMounted doesn't run493. UI still shows User 1's data!5051## Solution 1: Watch Route Params (Recommended)5253```vue54<script setup>55import { ref, watch } from 'vue'56import { useRoute } from 'vue-router'5758const route = useRoute()59const user = ref(null)60const loading = ref(false)6162// Watch for param changes - handles both initial load and navigation63watch(64() => route.params.id,65async (newId) => {66loading.value = true67user.value = await fetchUser(newId)68loading.value = false69},70{ immediate: true } // Run immediately for initial load71)72</script>73```7475## Solution 2: Use onBeforeRouteUpdate Guard7677```vue78<script setup>79import { ref, onMounted } from 'vue'80import { useRoute, onBeforeRouteUpdate } from 'vue-router'8182const route = useRoute()83const user = ref(null)8485async function loadUser(id) {86user.value = await fetchUser(id)87}8889// Initial load90onMounted(() => loadUser(route.params.id))9192// Handle param changes within same route93onBeforeRouteUpdate(async (to, from) => {94if (to.params.id !== from.params.id) {95await loadUser(to.params.id)96}97})98</script>99```100101## Solution 3: Force Component Re-creation with Key102103```vue104<!-- App.vue or parent component -->105<template>106<router-view :key="$route.fullPath" />107</template>108```109110**Tradeoffs:**111- Simple but less performant112- Destroys and recreates component on every param change113- Loses component state114- Use only when component state should reset completely115116## Solution 4: Composable for Route-Reactive Data117118```javascript119// composables/useRouteData.js120import { ref, watch } from 'vue'121import { useRoute } from 'vue-router'122123export function useRouteData(paramName, fetcher) {124const route = useRoute()125const data = ref(null)126const loading = ref(false)127const error = ref(null)128129watch(130() => route.params[paramName],131async (id) => {132if (!id) return133134loading.value = true135error.value = null136137try {138data.value = await fetcher(id)139} catch (e) {140error.value = e141} finally {142loading.value = false143}144},145{ immediate: true }146)147148return { data, loading, error }149}150```151152```vue153<!-- Usage in component -->154<script setup>155import { useRouteData } from '@/composables/useRouteData'156import { fetchUser } from '@/api/users'157158const { data: user, loading, error } = useRouteData('id', fetchUser)159</script>160```161162## What Triggers vs. What Doesn't163164| Navigation Type | Lifecycle Hooks | beforeRouteUpdate | Watch on params |165|----------------|-----------------|-------------------|-----------------|166| `/users/1` to `/posts/1` | YES | NO | YES |167| `/users/1` to `/users/2` | NO | YES | YES |168| `/users/1?tab=a` to `/users/1?tab=b` | NO | YES | NO (different watch) |169| `/users/1` to `/users/1` (same) | NO | NO | NO |170171## Key Points1721731. **Same route, different params = same component instance** - This is a performance optimization1742. **Lifecycle hooks only fire once** - When component first mounts1753. **Use `watch` with `immediate: true`** - Covers both initial load and updates1764. **`onBeforeRouteUpdate` is navigation-aware** - Good for data that must load before view updates1775. **`:key="route.fullPath"` is a sledgehammer** - Use only when necessary178179## Reference180- [Vue Router Dynamic Route Matching](https://router.vuejs.org/guide/essentials/dynamic-matching.html#reacting-to-params-changes)181- [Vue School: Reacting to Param Changes](https://vueschool.io/lessons/reacting-to-param-changes)182