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-simple-routing-cleanup.md
1---2title: Simple Hash Routing Requires Event Listener Cleanup3impact: MEDIUM4impactDescription: When implementing basic routing without Vue Router, forgetting to remove hashchange listeners causes memory leaks and multiple handler execution5type: gotcha6tags: [vue3, routing, events, memory-leak, cleanup]7---89# Simple Hash Routing Requires Event Listener Cleanup1011**Impact: MEDIUM** - When implementing basic client-side routing without Vue Router (using hash-based routing with `hashchange` events), you must clean up event listeners when the component unmounts. Failure to do so causes memory leaks and can result in multiple handlers firing after the component is recreated.1213## Task Checklist1415- [ ] Store event listener reference for cleanup16- [ ] Use onUnmounted to remove event listener17- [ ] Consider using Vue Router instead for production apps18- [ ] Test component mount/unmount cycles1920## The Problem2122```vue23<script setup>24import { ref, computed } from 'vue'25import Home from './Home.vue'26import About from './About.vue'2728const routes = {29'/': Home,30'/about': About31}3233const currentPath = ref(window.location.hash)3435// BUG: Event listener is never removed!36// Each time this component mounts, a NEW listener is added37// After mounting 5 times, you have 5 listeners running38window.addEventListener('hashchange', () => {39currentPath.value = window.location.hash40})4142const currentView = computed(() => {43return routes[currentPath.value.slice(1) || '/']44})45</script>46```4748**What happens:**491. Component mounts, adds listener502. Component unmounts (e.g., route change, v-if toggle)513. Component mounts again, adds ANOTHER listener524. Now TWO listeners respond to each hash change535. Eventually causes performance issues and memory leaks5455## Solution: Proper Cleanup with onUnmounted5657```vue58<script setup>59import { ref, computed, onUnmounted } from 'vue'60import Home from './Home.vue'61import About from './About.vue'62import NotFound from './NotFound.vue'6364const routes = {65'/': Home,66'/about': About67}6869const currentPath = ref(window.location.hash)7071// Store handler reference for cleanup72function handleHashChange() {73currentPath.value = window.location.hash74}7576// Add listener77window.addEventListener('hashchange', handleHashChange)7879// CRITICAL: Remove listener on unmount80onUnmounted(() => {81window.removeEventListener('hashchange', handleHashChange)82})8384const currentView = computed(() => {85return routes[currentPath.value.slice(1) || '/'] || NotFound86})87</script>88```8990## Solution: Using Options API9192```vue93<script>94import Home from './Home.vue'95import About from './About.vue'96import NotFound from './NotFound.vue'9798const routes = {99'/': Home,100'/about': About101}102103export default {104data() {105return {106currentPath: window.location.hash107}108},109110computed: {111currentView() {112return routes[this.currentPath.slice(1) || '/'] || NotFound113}114},115116mounted() {117// Store bound handler for cleanup118this.hashHandler = () => {119this.currentPath = window.location.hash120}121window.addEventListener('hashchange', this.hashHandler)122},123124beforeUnmount() {125// Clean up126window.removeEventListener('hashchange', this.hashHandler)127}128}129</script>130```131132## Solution: Composable for Reusable Hash Routing133134```javascript135// composables/useHashRouter.js136import { ref, computed, onUnmounted } from 'vue'137138export function useHashRouter(routes, notFoundComponent = null) {139const currentPath = ref(window.location.hash)140141function handleHashChange() {142currentPath.value = window.location.hash143}144145// Setup146window.addEventListener('hashchange', handleHashChange)147148// Cleanup - handled automatically when component unmounts149onUnmounted(() => {150window.removeEventListener('hashchange', handleHashChange)151})152153const currentView = computed(() => {154const path = currentPath.value.slice(1) || '/'155return routes[path] || notFoundComponent156})157158function navigate(path) {159window.location.hash = path160}161162return {163currentPath,164currentView,165navigate166}167}168```169170```vue171<!-- Usage -->172<script setup>173import { useHashRouter } from '@/composables/useHashRouter'174import Home from './Home.vue'175import About from './About.vue'176import NotFound from './NotFound.vue'177178const { currentView } = useHashRouter({179'/': Home,180'/about': About181}, NotFound)182</script>183184<template>185<component :is="currentView" />186</template>187```188189## When to Use Simple Routing vs Vue Router190191| Use Simple Hash Routing | Use Vue Router |192|------------------------|----------------|193| Learning/prototyping | Production apps |194| Very simple apps (2-3 pages) | Nested routes needed |195| No build step available | Navigation guards needed |196| Bundle size critical | Lazy loading needed |197| Static hosting only | History mode (clean URLs) |198199## Key Points2002011. **Always clean up event listeners** - Use onUnmounted or beforeUnmount2022. **Store handler reference** - Anonymous functions can't be removed2033. **Consider Vue Router for real apps** - It handles cleanup automatically2044. **Test unmount scenarios** - v-if toggling, hot module replacement2055. **Composables help encapsulate cleanup logic** - Reusable and automatic206207## Reference208- [Vue.js Routing Documentation](https://vuejs.org/guide/scaling-up/routing.html)209- [Vue Router Official Library](https://router.vuejs.org/)210