onBeforeRouteLeave Function | Vue Router
onBeforeRouteLeave is a composition API function that registers a navigation guard to be called before leaving the current route. It's useful for preventing accidental navigation away from pages with unsaved changes. 🛑
📋 Function Signature
typescript
function onBeforeRouteLeave(guard: NavigationGuard): void🎯 Purpose and Usage
The onBeforeRouteLeave function allows you to add navigation guards at the component level using Vue's composition API. It's particularly useful for:
- ✅ Form Protection - Prevent navigation with unsaved changes
- ✅ Confirmation Dialogs - Ask users to confirm navigation
- ✅ Data Saving - Save data before leaving the page
- ✅ Cleanup Operations - Perform cleanup before navigation
🔍 Parameters
guard
- Type:
NavigationGuard - Description: The navigation guard function to register
The guard function receives these parameters:
to- The target route locationfrom- The current route locationnext- Function to control navigation
💡 Practical Usage Examples
Basic Route Leave Guard
vue
<script setup>
import { onBeforeRouteLeave } from 'vue-router'
import { ref } from 'vue'
const hasUnsavedChanges = ref(false)
// Prevent navigation if there are unsaved changes
onBeforeRouteLeave((to, from, next) => {
if (hasUnsavedChanges.value) {
const answer = window.confirm(
'You have unsaved changes. Are you sure you want to leave?'
)
if (answer) {
next() // Allow navigation
} else {
next(false) // Cancel navigation
}
} else {
next() // Allow navigation
}
})
</script>Form Protection with Custom Dialog
vue
<script setup>
import { onBeforeRouteLeave } from 'vue-router'
import { ref } from 'vue'
const formData = ref({})
const originalData = ref({})
const showConfirmDialog = ref(false)
let resolveNavigation = null
const hasChanges = () => {
return JSON.stringify(formData.value) !== JSON.stringify(originalData.value)
}
onBeforeRouteLeave((to, from, next) => {
if (hasChanges()) {
showConfirmDialog.value = true
resolveNavigation = next
} else {
next()
}
})
// Handle user decision
const confirmLeave = () => {
showConfirmDialog.value = false
if (resolveNavigation) {
resolveNavigation()
resolveNavigation = null
}
}
const cancelLeave = () => {
showConfirmDialog.value = false
if (resolveNavigation) {
resolveNavigation(false)
resolveNavigation = null
}
}
</script>
<template>
<div>
<!-- Your form content -->
<form @submit.prevent="saveForm">
<input v-model="formData.name" />
<button type="submit">Save</button>
</form>
<!-- Confirmation dialog -->
<div v-if="showConfirmDialog" class="confirm-dialog">
<p>You have unsaved changes. Are you sure you want to leave?</p>
<button @click="confirmLeave">Leave</button>
<button @click="cancelLeave">Stay</button>
</div>
</div>
</template>Auto-Save Before Leaving
vue
<script setup>
import { onBeforeRouteLeave } from 'vue-router'
import { ref } from 'vue'
const formData = ref({})
const isSaving = ref(false)
const saveForm = async () => {
isSaving.value = true
try {
await api.saveForm(formData.value)
console.log('Form saved successfully')
} catch (error) {
console.error('Failed to save form:', error)
} finally {
isSaving.value = false
}
}
onBeforeRouteLeave(async (to, from, next) => {
if (isSaving.value) {
// Wait for current save to complete
await new Promise(resolve => {
const checkSave = () => {
if (!isSaving.value) {
resolve()
} else {
setTimeout(checkSave, 100)
}
}
checkSave()
})
}
// Auto-save before leaving
await saveForm()
next()
})
</script>🔧 Advanced Patterns
Conditional Route Guard
vue
<script setup>
import { onBeforeRouteLeave } from 'vue-router'
import { ref, computed } from 'vue'
const enableGuard = ref(true)
const unsavedChanges = ref(false)
// Only enable guard when needed
onBeforeRouteLeave((to, from, next) => {
if (!enableGuard.value) {
next()
return
}
if (unsavedChanges.value) {
const answer = window.confirm('Leave without saving?')
next(answer)
} else {
next()
}
})
// Temporarily disable guard for specific navigations
const navigateSafely = async (to) => {
enableGuard.value = false
await router.push(to)
// Re-enable guard after short delay
setTimeout(() => {
enableGuard.value = true
}, 100)
}
</script>Multiple Guards Management
vue
<script setup>
import { onBeforeRouteLeave } from 'vue-router'
import { ref } from 'vue'
const guards = ref(new Set())
const addRouteGuard = (guardFn) => {
guards.value.add(guardFn)
}
const removeRouteGuard = (guardFn) => {
guards.value.delete(guardFn)
}
// Main guard that coordinates multiple sub-guards
onBeforeRouteLeave(async (to, from, next) => {
const guardResults = []
for (const guard of guards.value) {
try {
const result = await new Promise((resolve) => {
guard(to, from, resolve)
})
guardResults.push(result)
} catch (error) {
console.error('Guard error:', error)
guardResults.push(false)
}
}
// Only allow navigation if all guards pass
const allPassed = guardResults.every(result => result !== false)
next(allPassed)
})
// Example usage: Add form validation guard
addRouteGuard((to, from, next) => {
if (formIsValid()) {
next()
} else {
const proceed = confirm('Form has errors. Leave anyway?')
next(proceed)
}
})
</script>Guard with Analytics
vue
<script setup>
import { onBeforeRouteLeave } from 'vue-router'
import { ref } from 'vue'
const navigationAttempts = ref(0)
onBeforeRouteLeave((to, from, next) => {
navigationAttempts.value++
// Track navigation attempts for analytics
analytics.track('route_leave_attempt', {
from: from.path,
to: to.path,
attemptNumber: navigationAttempts.value,
hasUnsavedChanges: hasUnsavedChanges.value
})
if (hasUnsavedChanges.value) {
const userDecision = window.confirm('Leave without saving?')
// Track user decision
analytics.track('route_leave_decision', {
decision: userDecision ? 'leave' : 'stay',
from: from.path,
to: to.path
})
next(userDecision)
} else {
next()
}
})
</script>🚨 Important Considerations
Guard Execution Order
typescript
// Route guards execute in this order:
// 1. Global beforeEach guards
// 2. Per-route beforeEnter guards
// 3. Component beforeRouteEnter guards (composition API)
// 4. Component beforeRouteUpdate guards (composition API)
// 5. Component beforeRouteLeave guards (composition API) ← This one!
// 6. Global afterEach hooks
// onBeforeRouteLeave runs after other guards but before navigationAsync Guard Handling
vue
<script setup>
import { onBeforeRouteLeave } from 'vue-router'
// ✅ Correct: Async guard with proper next() call
onBeforeRouteLeave(async (to, from, next) => {
await performCleanup()
next() // Don't forget to call next()
})
// ❌ Incorrect: Async guard without next()
onBeforeRouteLeave(async (to, from, next) => {
await performCleanup()
// Missing next() call - navigation will hang!
})
// ✅ Better: Always call next() in all code paths
onBeforeRouteLeave(async (to, from, next) => {
try {
await performCleanup()
next()
} catch (error) {
console.error('Cleanup failed:', error)
next(false) // Cancel navigation on error
}
})
</script>🔗 Related APIs
onBeforeRouteUpdate- Guard for route updates- Router Navigation Guards - Global and per-route guards
📚 Best Practices
Guard Cleanup
vue
<script setup>
import { onBeforeRouteLeave } from 'vue-router'
import { onUnmounted } from 'vue'
let guardActive = true
onBeforeRouteLeave((to, from, next) => {
if (!guardActive) {
next()
return
}
// Your guard logic here
if (hasUnsavedChanges.value) {
// Handle unsaved changes
} else {
next()
}
})
// Clean up guard when component unmounts
onUnmounted(() => {
guardActive = false
})
</script>Guard Testing
typescript
// Unit test for route leave guard
describe('RouteLeaveGuard', () => {
it('should prevent navigation with unsaved changes', async () => {
const mockNext = jest.fn()
const guard = createRouteLeaveGuard(true) // has unsaved changes
await guard({ path: '/new' }, { path: '/old' }, mockNext)
expect(mockNext).toHaveBeenCalledWith(false)
})
it('should allow navigation without unsaved changes', async () => {
const mockNext = jest.fn()
const guard = createRouteLeaveGuard(false) // no unsaved changes
await guard({ path: '/new' }, { path: '/old' }, mockNext)
expect(mockNext).toHaveBeenCalled()
})
})💡 Pro Tip: Use
onBeforeRouteLeavefor component-specific navigation protection. For application-wide protection, consider using global navigation guards or route meta fields with authentication checks.