# How to Properly Implement the 'Back' Button in Vue: A Technical Guide for Developers
Implementing the 'Back' button in Vue.js applications often seems straightforward, but in practice, it leads to serious navigation and browser history issues. Many developers mistakenly use router.push() instead of methods designed for moving through the existing history stack. This creates duplicate entries, breaks the browser's Back button behavior, and forms infinite transition loops. This article provides a technically precise guide to correctly implementing the 'Back' button, taking into account edge cases and user experience.
Why router.push() Is Not 'Back'
The router.push() method adds a new entry to the browser history. It's designed for navigating forward to a new route, even if that route was visited before. Unlike it, router.back() and router.go(-1) don't create new entries—they simply move the pointer through the existing history stack.
When you use router.push() for the 'Back' button, you're actually simulating a forward navigation, disguising it as a return. This leads to the following sequence:
- User: Home → Product List → Product A
- Clicks your 'Back' button with
router.push('/list') - History becomes: Home → Product List → Product A → Product List (duplicate)
Now, if the user clicks the browser's Back button, they'll go back to the 'Product A' page, not 'Home'. Clicking your button again will trigger push once more, and the cycle continues.
Main Navigation Methods in Vue Router
Vue Router provides three key methods for managing history:
router.push(location)— adds a new entry to the history.router.replace(location)— replaces the current entry without adding a new one.router.back()/router.go(-1)— moves back through the existing history.
For the 'Back' button in the UI, the only correct choice is router.back() or router.go(-1). They fully match the browser button's behavior and don't violate the stack's integrity.
Handling Edge Cases: When There's No History
In practice, users often land on a page directly—via an external link, page refresh, or after closing a tab. In such cases, the history stack may be empty or contain only one entry. Calling router.back() won't throw an error, but nothing will happen, leaving the user hanging.
To avoid this, provide a fallback route (fallbackRoute). The logic should check if back navigation is possible and, if not, redirect to a safe route—for example, the home page or parent section.
Universal 'Back' Button Component
Here's a production-ready component that supports all necessary scenarios:
<script setup lang="ts">
import { computed } from 'vue'
import { useRouter, useRoute, type RouteLocationRaw } from 'vue-router'
const props = defineProps<{
fallbackRoute?: RouteLocationRaw
beforeNavigate?: () => boolean | Promise<boolean>
beforeRouterPush?: () => void
}>()
const router = useRouter()
const route = useRoute()
const isSamePage = computed<boolean>(() => {
const fromRoute = router.options.history.state?.back
if (!fromRoute || typeof fromRoute !== 'string') return false
const currentPrefix = route.path.split('/')[1]
const fromPrefix = fromRoute.split('/')[1]
return currentPrefix === fromPrefix
})
const goBack = async () => {
if (props.beforeNavigate) {
const shouldProceed = await props.beforeNavigate()
if (!shouldProceed) return
}
if (props.beforeRouterPush) {
props.beforeRouterPush()
return
}
if (isSamePage.value) {
router.back()
} else if (props.fallbackRoute) {
void router.push(props.fallbackRoute)
} else {
void router.push({ name: 'home' })
}
}
</script>
<template>
<q-btn
icon="arrow_back"
@click="goBack"
aria-label="Onzad"
/>
</template>
This component solves several tasks at once:
- Supports confirmation before leaving (e.g., saving a draft)
- Distinguishes internal and external transitions by URL prefixes
- Provides a fallback route
- Allows fully overriding the logic if needed
Key Points
- Don't use
router.push()for the 'Back' button — it violates the browser history model. - Always provide fallback behavior for direct access cases.
- Check the transition context: returning within a section and from outside require different logic.
- Support canceling navigation via async hooks — critical for forms and editors.
- Sync behavior with the browser's native Back button — the user shouldn't notice a difference.
— Editorial Team
No comments yet.