Skip to content

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

typescript
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).

typescript
// 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.

typescript
// 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).

typescript
// Example: For URL "/page#section"
route.hash // "section"

query - Query Parameters

An object containing all URL query parameters.

typescript
// 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.

typescript
// 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.

typescript
// For route: { path: "/user", name: "user-list" }
route.name // "user-list"

// For unnamed routes
route.name // null or undefined

meta - Route Metadata

Custom metadata attached to the route configuration.

typescript
// For route: { path: "/admin", meta: { requiresAuth: true } }
route.meta // { requiresAuth: true }

// Access meta information
const requiresAuth = route.meta.requiresAuth // true

matched - Matched Route Records

An array of route records that match the current location, ordered from root to leaf.

typescript
// 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).

typescript
// 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.

typescript
// If redirected from "/old-path" to "/new-path"
route.redirectedFrom // RouteLocationNormalized for "/old-path"

💡 Practical Usage Examples

Route Information Display

vue
<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

vue
<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

typescript
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

typescript
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

vue
<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

typescript
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

typescript
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

typescript
// 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()
}

📚 Best Practices

1. Parameter Validation

typescript
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

typescript
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

typescript
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 matched array 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.

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