RouteLocationNormalized Interface | Vue Router
RouteLocationNormalized represents a normalized route location object that contains comprehensive information about a route after it has been processed and matched by the router. This interface is used throughout Vue Router for current and target route representations. 📍
📋 Interface Definition
interface RouteLocationNormalized {
// Core identification
readonly path: string
readonly fullPath: string
readonly hash: string
readonly query: LocationQuery
readonly params: RouteParams
// Route metadata
readonly name: string | symbol | null | undefined
readonly meta: RouteMeta
readonly matched: RouteRecordNormalized[]
readonly href: string
// Redirect information (if applicable)
readonly redirectedFrom?: RouteLocationNormalized
}🎯 Purpose and Usage
The RouteLocationNormalized interface provides a complete, standardized representation of a route location after all processing has been applied. It's the "gold standard" for route information within Vue Router.
Key Characteristics:
- ✅ Complete Information - Contains all route-related data
- ✅ Normalized Format - Consistent structure regardless of input format
- ✅ Reactive Ready - Suitable for use in reactive contexts
- ✅ Type Safe - Full TypeScript support with proper typing
🔍 Property Details
Core Identification Properties
path - Route Path
The path portion of the URL (without query string or hash).
// Example: For URL "/user/123?tab=profile#section"
route.path // "/user/123"fullPath - Complete URL Path
The full path including query string and hash fragment.
// Example: For URL "/user/123?tab=profile#section"
route.fullPath // "/user/123?tab=profile#section"hash - URL Hash Fragment
The hash portion of the URL (without the # character).
// Example: For URL "/page#section"
route.hash // "section"query - Query Parameters
An object containing all URL query parameters.
// Example: For URL "/search?q=vue&page=2"
route.query // { q: "vue", page: "2" }
// Access specific parameters
const searchTerm = route.query.q // "vue"
const pageNumber = route.query.page // "2"params - Route Parameters
Dynamic parameters extracted from the route path.
// For route definition: { path: "/user/:id" }
// And URL: "/user/123"
route.params // { id: "123" }
// For nested params: { path: "/blog/:category/:post" }
// And URL: "/blog/technology/123"
route.params // { category: "technology", post: "123" }Route Metadata Properties
name - Route Name
The name of the route if defined in the route configuration.
// For route: { path: "/user", name: "user-list" }
route.name // "user-list"
// For unnamed routes
route.name // null or undefinedmeta - Route Metadata
Custom metadata attached to the route configuration.
// For route: { path: "/admin", meta: { requiresAuth: true } }
route.meta // { requiresAuth: true }
// Access meta information
const requiresAuth = route.meta.requiresAuth // truematched - Matched Route Records
An array of route records that match the current location, ordered from root to leaf.
// For nested routes: /admin/users/list
route.matched // [adminRoute, usersRoute, listRoute]
// Check if specific route is matched
const isAdminRoute = route.matched.some(record =>
record.meta?.requiresAdmin
)href - Complete URL
The full URL including protocol, host, and path (useful for external links).
// Example value
route.href // "https://example.com/user/123?tab=profile"Redirect Information
redirectedFrom - Redirect Source
If the current route was reached via a redirect, this property contains the original route.
// If redirected from "/old-path" to "/new-path"
route.redirectedFrom // RouteLocationNormalized for "/old-path"💡 Practical Usage Examples
Route Information Display
<template>
<div class="route-info">
<p><strong>Path:</strong> {{ route.path }}</p>
<p><strong>Full Path:</strong> {{ route.fullPath }}</p>
<p><strong>Query:</strong> {{ JSON.stringify(route.query) }}</p>
<p><strong>Params:</strong> {{ JSON.stringify(route.params) }}</p>
<p><strong>Route Name:</strong> {{ route.name }}</p>
</div>
</template>
<script setup>
import { useRoute } from 'vue-router'
const route = useRoute()
</script>Parameter-Based Logic
<script setup>
import { useRoute } from 'vue-router'
import { computed, watch } from 'vue'
const route = useRoute()
// Extract and type parameters
const userId = computed(() => {
const id = route.params.id
return id ? String(id) : null
})
const searchQuery = computed(() => route.query.q || '')
// Watch for parameter changes
watch(
() => route.params.id,
(newId, oldId) => {
if (newId && newId !== oldId) {
loadUserData(newId)
}
}
)
// Meta-based conditional rendering
const requiresAuth = computed(() => route.meta.requiresAuth === true)
const pageTitle = computed(() => route.meta.title || 'Default Title')
</script>Advanced Route Analysis
class RouteAnalyzer {
analyzeRoute(route: RouteLocationNormalized) {
return {
// Basic information
path: route.path,
fullPath: route.fullPath,
// Parameter analysis
hasParams: Object.keys(route.params).length > 0,
paramCount: Object.keys(route.params).length,
// Query analysis
hasQuery: Object.keys(route.query).length > 0,
queryKeys: Object.keys(route.query),
// Meta analysis
metaKeys: Object.keys(route.meta),
requiresAuth: route.meta.requiresAuth === true,
// Matching analysis
depth: route.matched.length,
matchedNames: route.matched.map(record => record.name).filter(Boolean),
// Redirect information
wasRedirected: !!route.redirectedFrom,
originalPath: route.redirectedFrom?.path
}
}
isRouteActive(route: RouteLocationNormalized, targetPath: string): boolean {
return route.path === targetPath ||
route.matched.some(record => record.path === targetPath)
}
}🔧 Advanced Patterns
Route-Based Feature Flags
class FeatureManager {
isFeatureEnabled(route: RouteLocationNormalized, feature: string): boolean {
// Check route-specific feature flags
const routeFeatures = route.meta.features as string[] || []
// Check user permissions for the current route context
const userHasAccess = this.checkUserPermission(route, feature)
return routeFeatures.includes(feature) && userHasAccess
}
getEnabledFeatures(route: RouteLocationNormalized): string[] {
const baseFeatures = ['basic-navigation', 'breadcrumbs']
const routeFeatures = route.meta.features as string[] || []
return [...baseFeatures, ...routeFeatures].filter(feature =>
this.isFeatureEnabled(route, feature)
)
}
}Dynamic Content Based on Route
<script setup>
import { useRoute } from 'vue-router'
import { computed } from 'vue'
const route = useRoute()
// Dynamic page title
const pageTitle = computed(() => {
if (route.meta.title) {
return typeof route.meta.title === 'function'
? route.meta.title(route)
: route.meta.title
}
// Fallback to route name or path
return route.name
? String(route.name).replace(/-/g, ' ')
: route.path.split('/').pop() || 'Home'
})
// Dynamic breadcrumbs
const breadcrumbs = computed(() => {
return route.matched.map((record, index) => ({
name: record.meta?.breadcrumb || String(record.name || record.path),
path: record.path,
isLast: index === route.matched.length - 1
}))
})
// Dynamic sidebar visibility
const showSidebar = computed(() =>
route.meta.showSidebar !== false && route.matched.length > 1
)
</script>Route Comparison Utilities
function createRouteComparator() {
return {
// Check if two routes are essentially the same (ignoring query/hash)
isSameRoute(route1: RouteLocationNormalized, route2: RouteLocationNormalized): boolean {
return route1.path === route2.path &&
route1.name === route2.name
},
// Check if route has changed significantly
hasRouteChanged(
oldRoute: RouteLocationNormalized,
newRoute: RouteLocationNormalized
): boolean {
return oldRoute.path !== newRoute.path ||
oldRoute.name !== newRoute.name ||
JSON.stringify(oldRoute.params) !== JSON.stringify(newRoute.params)
},
// Extract changing parameters between routes
getChangedParams(
oldRoute: RouteLocationNormalized,
newRoute: RouteLocationNormalized
): string[] {
const changed: string[] = []
for (const key in newRoute.params) {
if (oldRoute.params[key] !== newRoute.params[key]) {
changed.push(key)
}
}
return changed
}
}
}🚨 Important Considerations
Reactivity and Performance
import { useRoute } from 'vue-router'
import { computed, watch } from 'vue'
const route = useRoute()
// ✅ Efficient - computed properties only recompute when needed
const userId = computed(() => route.params.id)
const searchTerm = computed(() => route.query.q)
// ❌ Inefficient - direct access in functions called frequently
function getUserId() {
return route.params.id // This access triggers reactivity every time
}
// ✅ Better - use computed or watch specific properties
watch(() => route.params.id, (newId) => {
// Only runs when ID actually changes
loadUserData(newId)
})Type Safety with Route Meta
// Define your application's meta structure
declare module 'vue-router' {
interface RouteMeta {
requiresAuth?: boolean
title?: string
breadcrumb?: string
features?: string[]
// Add other meta properties your app uses
}
}
// Now TypeScript will validate meta property access
const route = useRoute()
// ✅ Type-safe access
const requiresAuth = route.meta.requiresAuth // boolean | undefined
// ❌ TypeScript error for unknown properties
// const unknownProp = route.meta.unknownProperty // Error!
// ✅ Proper type checking
if (route.meta.requiresAuth) {
// TypeScript knows requiresAuth is true here
checkAuthentication()
}🔗 Related APIs
RouteLocationNormalizedLoaded- Loaded route versionuseRoute()- Hook to access current routeRouteRecordNormalized- Matched route records
📚 Best Practices
1. Parameter Validation
function validateRouteParams(route: RouteLocationNormalized) {
const { params, path } = route
// Validate required parameters
if (path.includes('/:id') && !params.id) {
throw new Error('ID parameter is required for this route')
}
// Type conversion with validation
const page = parseInt(params.page as string)
if (isNaN(page) || page < 1) {
throw new Error('Invalid page parameter')
}
return { ...params, page }
}2. Route-Based Authorization
class RouteAuthorizer {
canAccessRoute(route: RouteLocationNormalized, user: User): boolean {
// Check authentication requirement
if (route.meta.requiresAuth && !user.isAuthenticated) {
return false
}
// Check role-based access
const requiredRoles = route.meta.requiredRoles as string[] || []
if (requiredRoles.length > 0 && !user.roles.some(role =>
requiredRoles.includes(role)
)) {
return false
}
// Check feature flags
const requiredFeatures = route.meta.features as string[] || []
if (requiredFeatures.length > 0 && !user.features.some(feature =>
requiredFeatures.includes(feature)
)) {
return false
}
return true
}
}3. Route Analytics
class RouteAnalytics {
trackRouteView(route: RouteLocationNormalized) {
const analyticsData = {
path: route.path,
fullPath: route.fullPath,
name: route.name,
params: route.params,
query: route.query,
meta: route.meta,
timestamp: Date.now(),
matchedDepth: route.matched.length
}
// Send to analytics service
analytics.track('page_view', analyticsData)
// Update page title for SEO
if (route.meta.title) {
document.title = typeof route.meta.title === 'function'
? route.meta.title(route)
: route.meta.title
}
}
}💡 Pro Tip: Use the
matchedarray to understand the complete routing hierarchy for the current location. This is especially useful for breadcrumb generation, permission checking across nested routes, and understanding the complete context of the current navigation.