Programmatic Navigation
Learn how to navigate between routes programmatically using Vue Router's navigation methods.
Introduction
Besides using <router-link> for declarative navigation, you can navigate programmatically using the router's instance methods. This is useful for navigation based on user interactions, form submissions, or business logic.
Accessing the Router
Options API
export default {
methods: {
navigateToUser() {
this.$router.push('/user/123')
}
}
}2
3
4
5
6
7
Composition API
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
const navigateToUser = () => {
router.push('/user/123')
}
</script>2
3
4
5
6
7
8
9
Navigation Methods
router.push()
Navigate to a different location by pushing a new entry into the history stack:
// String path
router.push('/users/eduardo')
// Object with path
router.push({ path: '/users/eduardo' })
// Named route with params
router.push({ name: 'user', params: { username: 'eduardo' } })
// With query parameters
router.push({ path: '/register', query: { plan: 'private' } })
// Results in: /register?plan=private
// With hash
router.push({ path: '/about', hash: '#team' })
// Results in: /about#team2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Equivalent to clicking <router-link :to="...">
| Declarative | Programmatic |
|---|---|
<router-link :to="..."> | router.push(...) |
router.replace()
Navigate without pushing a new history entry (replaces the current entry):
// Replace current location
router.replace('/home')
// Object syntax
router.replace({ path: '/home' })
// Add replace: true to push
router.push({ path: '/home', replace: true })2
3
4
5
6
7
8
| Declarative | Programmatic |
|---|---|
<router-link :to="..." replace> | router.replace(...) |
router.go()
Traverse the history stack:
// Go forward one record
router.go(1)
// Same as router.forward()
// Go back one record
router.go(-1)
// Same as router.back()
// Go forward 3 records
router.go(3)
// Go back 3 records
router.go(-3)
// Fails silently if not enough records
router.go(-100)
router.go(100)2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Navigation Options
Using Parameters
When using named routes, always prefer name + params over manual path construction:
const username = 'eduardo'
// ✅ Recommended: Automatic URL encoding
router.push({ name: 'user', params: { username } })
// ✅ Manual path construction
router.push(`/user/${username}`)
router.push({ path: `/user/${username}` })
// ❌ Wrong: params ignored when path is provided
router.push({ path: '/user', params: { username } })2
3
4
5
6
7
8
9
10
11
Parameter Types
Parameters should be strings, numbers, or arrays of these:
// ✅ Valid parameter types
router.push({ name: 'user', params: { id: '123' } })
router.push({ name: 'user', params: { id: 123 } })
router.push({ name: 'tags', params: { tags: ['vue', 'router'] } })
// ⚠️ Other types are automatically stringified
router.push({ name: 'user', params: { id: { value: 123 } } })
// Results in: id: "[object Object]"
// ✅ For optional params, use empty string or null
router.push({ name: 'user', params: { id: '' } }) // Removes param
router.push({ name: 'user', params: { id: null } }) // Removes param2
3
4
5
6
7
8
9
10
11
12
Query Parameters
Query parameters can be combined with any navigation method:
// Simple query
router.push({ path: '/search', query: { q: 'vue' } })
// Results in: /search?q=vue
// Multiple query parameters
router.push({
path: '/products',
query: {
category: 'electronics',
sort: 'price',
page: 1
}
})
// Results in: /products?category=electronics&sort=price&page=1
// Array values
router.push({
path: '/filter',
query: { tags: ['vue', 'javascript'] }
})
// Results in: /filter?tags=vue&tags=javascript2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Async Navigation
All navigation methods return a Promise:
// Wait for navigation to complete
try {
await router.push('/user/123')
console.log('Navigation successful')
} catch (error) {
console.log('Navigation failed:', error)
}
// Handle navigation result
router.push('/user/123')
.then(() => {
console.log('Navigation successful')
})
.catch(error => {
console.log('Navigation failed:', error)
})2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Navigation Failures
Navigation can fail for several reasons:
import { NavigationFailureType, isNavigationFailure } from 'vue-router'
try {
await router.push('/user/123')
} catch (error) {
if (isNavigationFailure(error, NavigationFailureType.aborted)) {
console.log('Navigation was aborted')
} else if (isNavigationFailure(error, NavigationFailureType.cancelled)) {
console.log('Navigation was cancelled')
} else if (isNavigationFailure(error, NavigationFailureType.duplicated)) {
console.log('Navigation was duplicated')
}
}2
3
4
5
6
7
8
9
10
11
12
13
Practical Examples
Form Submission Navigation
<template>
<form @submit.prevent="submitForm">
<input v-model="username" placeholder="Username" required />
<button type="submit" :disabled="loading">
{{ loading ? 'Creating...' : 'Create User' }}
</button>
</form>
</template>
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const username = ref('')
const loading = ref(false)
const submitForm = async () => {
loading.value = true
try {
// Create user via API
const user = await createUser(username.value)
// Navigate to user profile
await router.push({
name: 'user-profile',
params: { id: user.id }
})
} catch (error) {
console.error('Failed to create user:', error)
} finally {
loading.value = false
}
}
</script>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
Conditional Navigation
<script setup>
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
const handleNavigation = async (targetRoute) => {
// Check if user has unsaved changes
if (hasUnsavedChanges.value) {
const confirmed = await showConfirmDialog(
'You have unsaved changes. Are you sure you want to leave?'
)
if (!confirmed) {
return
}
}
// Navigate to target route
router.push(targetRoute)
}
const goBack = () => {
// Go back or to home if no history
if (window.history.length > 1) {
router.go(-1)
} else {
router.push('/')
}
}
</script>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Dynamic Route Building
<script setup>
import { computed } from 'vue'
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
// Build routes dynamically based on current route
const navigationItems = computed(() => [
{
name: 'Dashboard',
route: { name: 'dashboard' }
},
{
name: 'Profile',
route: {
name: 'user-profile',
params: { id: route.params.id }
}
},
{
name: 'Settings',
route: {
name: 'user-settings',
params: { id: route.params.id },
query: { tab: 'general' }
}
}
])
const navigateTo = (item) => {
router.push(item.route)
}
</script>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Navigation with State
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
const openUserInNewContext = (userId) => {
// Navigate with additional state
router.push({
name: 'user-detail',
params: { id: userId },
query: {
returnTo: route.fullPath,
context: 'search-results'
}
})
}
const returnToPrevious = () => {
const returnTo = route.query.returnTo
if (returnTo) {
router.push(returnTo)
} else {
router.go(-1)
}
}
</script>2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Best Practices
1. Use Named Routes for Complex Navigation
// ✅ Good: Clear and maintainable
router.push({
name: 'product-detail',
params: { id: productId },
query: { variant: selectedVariant }
})
// ❌ Avoid: Hard to maintain
router.push(`/products/${productId}?variant=${selectedVariant}`)2
3
4
5
6
7
8
9
2. Handle Navigation Errors
const navigateWithErrorHandling = async (route) => {
try {
await router.push(route)
} catch (error) {
if (!isNavigationFailure(error)) {
// Unexpected error
console.error('Navigation error:', error)
showErrorMessage('Navigation failed. Please try again.')
}
}
}2
3
4
5
6
7
8
9
10
11
3. Validate Before Navigation
const navigateToProtectedRoute = async (route) => {
// Check authentication
if (!isAuthenticated.value) {
router.push({ name: 'login', query: { redirect: route.fullPath } })
return
}
// Check permissions
if (!hasPermission(route.meta.requiredPermission)) {
router.push({ name: 'unauthorized' })
return
}
router.push(route)
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
4. Preserve Query Parameters
const navigatePreservingQuery = (newPath) => {
router.push({
path: newPath,
query: route.query // Preserve current query parameters
})
}2
3
4
5
6
Common Pitfalls
- Mixing path and params - Use either
pathorname+params, not both - Forgetting await - Navigation is asynchronous, use
awaitwhen needed - Not handling errors - Always handle potential navigation failures
- Incorrect parameter types - Use strings/numbers for route parameters
Next Steps
- Learn about Named Routes for cleaner navigation
- Explore Navigation Guards for route protection
- Understand Route Transitions for smooth navigation