Skip to content

Typed Routes | Vue Router - Type-Safe Navigation with TypeScript ๐Ÿ”’ โ€‹

Compile-time route safety - Catch routing errors before they happen with full TypeScript integration!

๐ŸŽฏ Overview โ€‹

Typed routes provide type safety for your Vue Router navigation, ensuring that route names, parameters, and queries are validated at compile time. This eliminates runtime errors and provides excellent developer experience with autocomplete and type checking.

๐Ÿ“‹ Key Benefits โ€‹

  • Type Safety: Catch routing errors during development
  • Autocomplete: Intelligent code completion for route names and parameters
  • Refactoring Safety: Safe refactoring of route names and structures
  • Better DX: Improved developer experience with type hints

๐Ÿ’ก When to Use Typed Routes โ€‹

๐Ÿš€ TypeScript Projects โ€‹

Large Applications

  • Applications with complex routing structures
  • Teams working on the same codebase
  • Projects requiring high reliability

Enterprise Development

  • Strict type checking requirements
  • Code maintenance and refactoring
  • API consistency across teams

๐Ÿ”ง Basic Setup โ€‹

typescript
// router.ts
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      name: 'home',
      component: () => import('./views/Home.vue')
    },
    {
      path: '/user/:id',
      name: 'user',
      component: () => import('./views/User.vue')
    },
    {
      path: '/products/:category/:productId',
      name: 'product',
      component: () => import('./views/Product.vue')
    }
  ]
})

// Export typed router instance
export default router

// Type definitions for route names
export type RouteNames = 'home' | 'user' | 'product'

๐Ÿš€ Getting Started โ€‹

Basic Typed Navigation โ€‹

typescript
// In your components
import { useRouter } from 'vue-router'
import type { RouteNames } from '../router'

const router = useRouter()

// Type-safe navigation
router.push({ name: 'home' }) // โœ… Valid
router.push({ name: 'user', params: { id: 123 } }) // โœ… Valid
router.push({ name: 'nonexistent' }) // โŒ Type error!

Advanced Route Parameter Typing โ€‹

typescript
// Enhanced route definitions with parameter types
interface RouteParams {
  home: never // No parameters
  user: { id: string | number }
  product: { category: string; productId: string }
}

interface RouteQueries {
  home: { search?: string }
  user: { tab?: 'profile' | 'settings' }
  product: { variant?: string; color?: string }
}

// Usage with full type safety
router.push({
  name: 'user',
  params: { id: 123 }, // โœ… Type checked
  query: { tab: 'profile' } // โœ… Type checked
})

router.push({
  name: 'user',
  params: { id: 123, extra: 'invalid' } // โŒ Type error!
})

๐Ÿ”ง Advanced Techniques โ€‹

Example 1: Comprehensive Route Typing โ€‹

typescript
// types/router.d.ts
import 'vue-router'

// Define route meta structure
declare module 'vue-router' {
  interface RouteMeta {
    requiresAuth?: boolean
    title?: string
    layout?: 'default' | 'admin' | 'auth'
  }

  // Define route names and their parameters
  interface _RouteRecordBase {
    // This enhances the base route record type
  }
}

// Route configuration with full typing
const routes = [
  {
    path: '/',
    name: 'home',
    component: () => import('./views/Home.vue'),
    meta: { title: 'Home Page', requiresAuth: false }
  },
  {
    path: '/login',
    name: 'login',
    component: () => import('./views/Login.vue'),
    meta: { title: 'Login', layout: 'auth' }
  },
  {
    path: '/dashboard',
    name: 'dashboard',
    component: () => import('./views/Dashboard.vue'),
    meta: { title: 'Dashboard', requiresAuth: true }
  },
  {
    path: '/user/:userId',
    name: 'user-profile',
    component: () => import('./views/UserProfile.vue'),
    meta: { requiresAuth: true },
    props: true // Enable props for route parameters
  }
] as const // 'as const' for literal type inference

// Extract route names for type safety
type AppRouteNames = typeof routes[number]['name']

Example 2: Utility Functions for Type-Safe Navigation โ€‹

typescript
// utils/navigation.ts
import { useRouter, useRoute } from 'vue-router'
import type { RouteParams, RouteQueries } from '../types/router'

export function useTypedRouter() {
  const router = useRouter()
  const route = useRoute()

  // Type-safe push method
  function push<T extends keyof RouteParams>(
    name: T,
    params?: RouteParams[T],
    query?: RouteQueries[T]
  ) {
    return router.push({
      name: name as string,
      params,
      query
    })
  }

  // Type-safe replace method
  function replace<T extends keyof RouteParams>(
    name: T,
    params?: RouteParams[T],
    query?: RouteQueries[T]
  ) {
    return router.replace({
      name: name as string,
      params,
      query
    })
  }

  // Type-safe current route checker
  function isCurrentRoute<T extends keyof RouteParams>(
    name: T,
    params?: Partial<RouteParams[T]>
  ): boolean {
    if (route.name !== name) return false
    
    if (params) {
      return Object.keys(params).every(key => 
        route.params[key] === params[key as keyof typeof params]
      )
    }
    
    return true
  }

  return {
    push,
    replace,
    isCurrentRoute,
    router, // Original router for advanced use cases
    route   // Current route with type information
  }
}

// Usage in components
const { push, replace, isCurrentRoute } = useTypedRouter()

// โœ… Type-safe navigation
push('user-profile', { userId: '123' })
push('home') // No parameters needed

// โœ… Type checking
push('user-profile', { userId: 123 }) // โœ…
push('user-profile', { invalidParam: 'value' }) // โŒ Type error!

๐Ÿ› ๏ธ Practical Examples โ€‹

Example 1: E-commerce Application with Typed Routes โ€‹

typescript
// router/types.ts
export interface EcommerceRouteParams {
  'home': never
  'products': { category?: string }
  'product-detail': { productId: string; variant?: string }
  'cart': never
  'checkout': { step?: 'shipping' | 'payment' | 'review' }
  'order-confirmation': { orderId: string }
}

export interface EcommerceRouteQueries {
  'home': { search?: string; sort?: 'price' | 'name' | 'rating' }
  'products': { 
    page?: number; 
    sort?: 'price' | 'name' | 'rating';
    filter?: string 
  }
  'product-detail': { review?: string; image?: number }
  'cart': { coupon?: string }
  'checkout': { guest?: boolean }
  'order-confirmation': { email?: string }
}

// router/index.ts
const routes = [
  {
    path: '/',
    name: 'home',
    component: () => import('./views/Home.vue')
  },
  {
    path: '/products/:category?',
    name: 'products',
    component: () => import('./views/Products.vue'),
    props: route => ({
      category: route.params.category,
      page: parseInt(route.query.page as string) || 1,
      sort: route.query.sort || 'name'
    })
  },
  {
    path: '/product/:productId/:variant?',
    name: 'product-detail',
    component: () => import('./views/ProductDetail.vue')
  },
  {
    path: '/cart',
    name: 'cart',
    component: () => import('./views/Cart.vue')
  },
  {
    path: '/checkout/:step?',
    name: 'checkout',
    component: () => import('./views/Checkout.vue')
  },
  {
    path: '/order/:orderId',
    name: 'order-confirmation',
    component: () => import('./views/OrderConfirmation.vue')
  }
] as const

// Export route name type
export type EcommerceRouteName = typeof routes[number]['name']

Example 2: Admin Dashboard with Permission-Based Routing โ€‹

typescript
// types/admin-router.ts
export interface AdminRouteParams {
  'admin-dashboard': never
  'admin-users': { page?: number }
  'admin-user-detail': { userId: string }
  'admin-settings': { section?: 'general' | 'security' | 'notifications' }
  'admin-reports': { type?: 'sales' | 'users' | 'performance' }
}

export interface AdminRouteMeta {
  'admin-dashboard': { requires: 'admin' | 'super-admin' }
  'admin-users': { requires: 'admin' | 'super-admin' }
  'admin-user-detail': { requires: 'super-admin' }
  'admin-settings': { requires: 'super-admin' }
  'admin-reports': { requires: 'admin' | 'super-admin' }
}

// Navigation guard with type safety
router.beforeEach((to, from) => {
  const routeMeta = to.meta as AdminRouteMeta[keyof AdminRouteMeta]
  
  if (routeMeta.requires) {
    const userRole = getUserRole() // Your auth logic
    
    if (!userRole || !hasPermission(userRole, routeMeta.requires)) {
      return { name: 'unauthorized' }
    }
  }
})

function hasPermission(userRole: string, required: string | string[]): boolean {
  const requiredArray = Array.isArray(required) ? required : [required]
  return requiredArray.includes(userRole)
}

๐Ÿ” TypeScript Configuration โ€‹

tsconfig.json Recommendations โ€‹

json
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitOverride": true
  }
}

Volar Configuration for Template Type Checking โ€‹

json
// tsconfig.json or vue-tsconfig.json
{
  "vueCompilerOptions": {
    "target": 3,
    "plugins": [
      "@volar/vue-language-plugin-pug"
    ]
  }
}

๐Ÿš€ Best Practices โ€‹

โœ… Do โ€‹

  • Use Const Assertions: as const for literal type inference
  • Define Clear Interfaces: Separate params, queries, and meta types
  • Use Utility Types: Leverage TypeScript's advanced type features
  • Test Type Safety: Ensure type errors are caught during development
  • Document Complex Types: Comment complex type definitions

โŒ Don't โ€‹

  • Use Any Types: Avoid any in route definitions
  • Ignore Template Types: Configure Volar for template type checking
  • Overcomplicate: Keep type definitions manageable
  • Forget Runtime Checks: TypeScript doesn't replace runtime validation
  • Vue 3 Composition API: Enhanced type inference capabilities
  • Volar Extension: Template type checking for Vue components
  • TypeScript Utility Types: Partial, Required, Pick, etc.
  • Vue Router Type Definitions: Built-in type definitions to extend

๐Ÿ’ก Pro Tip

Combine typed routes with Vue's `defineProps` and `defineEmits` for end-to-end type safety from your component props through your route navigation and back!

Ready for type-safe routing? Start implementing typed routes to eliminate routing errors and enhance your development experience! ๐Ÿ”’

๐Ÿš€ Vue Router - ่ฎฉๅ‰็ซฏ่ทฏ็”ฑๅ˜ๅพ—็ฎ€ๅ•่€Œๅผบๅคง | ๆž„ๅปบ็ŽฐไปฃๅŒ–ๅ•้กตๅบ”็”จ็š„ๆœ€ไฝณ้€‰ๆ‹ฉ