Skip to content

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 location
  • from - The current route location
  • next - 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 navigation

Async 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>
  • 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 onBeforeRouteLeave for component-specific navigation protection. For application-wide protection, consider using global navigation guards or route meta fields with authentication checks.

🚀 Vue Router - 让前端路由变得简单而强大 | 构建现代化单页应用的最佳选择