Skip to content

Routers User Guide

This guide shows you how to use the router utilities in real Vue.js applications with practical examples and patterns.

Vue Component Usage

<template>
  <div>
    <!-- Search form that updates URL -->
    <form @submit.prevent="search">
      <input v-model="searchQuery" placeholder="Search users..." />
      <button type="submit">Search</button>
    </form>

    <!-- Filter controls -->
    <div class="filters">
      <select v-model="statusFilter" @change="updateFilters">
        <option value="">All Status</option>
        <option value="active">Active</option>
        <option value="inactive">Inactive</option>
      </select>

      <select v-model="roleFilter" @change="updateFilters">
        <option value="">All Roles</option>
        <option value="admin">Admin</option>
        <option value="user">User</option>
      </select>
    </div>
  </div>
</template>

<script setup>
import { replaceRouteQuery, restoreRouteQuery } from 'vue-fastedgy'
import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'

const route = useRoute()
const router = useRouter()

const searchQuery = ref('')
const statusFilter = ref('')
const roleFilter = ref('')

// Restore state from URL on component mount
onMounted(() => {
  searchQuery.value = restoreRouteQuery('q', route, 'search', '')
  statusFilter.value = restoreRouteQuery('status', route, 'search', '')
  roleFilter.value = restoreRouteQuery('role', route, 'search', '')
})

const search = () => {
  replaceRouteQuery(router, {
    q: searchQuery.value || undefined,
    page: undefined // Reset pagination on new search
  }, route, 'search')
}

const updateFilters = () => {
  replaceRouteQuery(router, {
    status: statusFilter.value || undefined,
    role: roleFilter.value || undefined,
    page: undefined // Reset pagination on filter change
  }, route, 'search')
}
</script>

Login Redirect Flow

Handle authentication redirects properly:

<!-- LoginPage.vue -->
<script setup>
import { addRedirect, getRedirect, useAuthStore } from 'vue-fastedgy'
import { useRoute, useRouter } from 'vue-router'

const authStore = useAuthStore()
const route = useRoute()
const router = useRouter()

const handleLogin = async (credentials) => {
  const result = await authStore.login(credentials)

  if (result.success) {
    // Get intended destination or default to dashboard
    const redirectTo = getRedirect(route, { name: 'Dashboard' })
    router.push(redirectTo)
  } else {
    console.error(result.message)
  }
}
</script>
// In router guards
import { addRedirect, useAuthStore } from 'vue-fastedgy'

router.beforeEach((to, from, next) => {
  const authStore = useAuthStore()

  if (to.meta.requiresAuth && !authStore.isAuthenticated) {
    // Save intended destination
    const loginRoute = addRedirect(from, { name: 'Login' })
    next(loginRoute)
  } else {
    next()
  }
})

Advanced Filter Management

Handle complex filter state with URL synchronization:

<template>
  <div class="data-table">
    <!-- Filter controls -->
    <div class="filter-bar">
      <div class="filter-group">
        <label>Search:</label>
        <input v-model="filters.search" @input="updateFiltersDebounced" />
      </div>

      <div class="filter-group">
        <label>Date Range:</label>
        <input v-model="filters.dateFrom" type="date" @change="updateFilters" />
        <input v-model="filters.dateTo" type="date" @change="updateFilters" />
      </div>

      <div class="filter-group">
        <label>Categories:</label>
        <select v-model="filters.categories" multiple @change="updateFilters">
          <option v-for="cat in availableCategories" :key="cat.id" :value="cat.id">
            {{ cat.name }}
          </option>
        </select>
      </div>

      <button @click="clearFilters">Clear All</button>
    </div>

    <!-- Results -->
    <div class="results">
      <div v-for="item in filteredResults" :key="item.id">
        {{ item.name }}
      </div>
    </div>
  </div>
</template>

<script setup>
import { replaceRouteQuery, restoreRouteQuery } from 'vue-fastedgy'
import { reactive, watch, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { debounce } from 'lodash-es'

const route = useRoute()
const router = useRouter()

const filters = reactive({
  search: '',
  dateFrom: '',
  dateTo: '',
  categories: []
})

const filteredResults = ref([])
const availableCategories = ref([])

// Restore filters from URL on mount
onMounted(() => {
  filters.search = restoreRouteQuery('search', route, 'filters', '')
  filters.dateFrom = restoreRouteQuery('dateFrom', route, 'filters', '')
  filters.dateTo = restoreRouteQuery('dateTo', route, 'filters', '')
  filters.categories = restoreRouteQuery('categories', route, 'filters', [], 'object')

  // Initial data load
  loadData()
})

// Update URL when filters change
const updateFilters = () => {
  const filterData = {
    search: filters.search || undefined,
    dateFrom: filters.dateFrom || undefined,
    dateTo: filters.dateTo || undefined,
    categories: filters.categories.length > 0 ? filters.categories : undefined
  }

  replaceRouteQuery(router, filterData, route, 'filters')
  loadData()
}

// Debounced update for search input
const updateFiltersDebounced = debounce(updateFilters, 300)

const clearFilters = () => {
  Object.keys(filters).forEach(key => {
    if (Array.isArray(filters[key])) {
      filters[key] = []
    } else {
      filters[key] = ''
    }
  })
  updateFilters()
}

const loadData = async () => {
  // Load data based on current filters
  const response = await fetch('/api/data?' + new URLSearchParams(filters))
  filteredResults.value = await response.json()
}

// Watch for external URL changes (browser back/forward)
watch(() => route.query, () => {
  // Re-sync filters from URL
  filters.search = restoreRouteQuery('search', route, 'filters', '')
  filters.dateFrom = restoreRouteQuery('dateFrom', route, 'filters', '')
  filters.dateTo = restoreRouteQuery('dateTo', route, 'filters', '')
  filters.categories = restoreRouteQuery('categories', route, 'filters', [], 'object')

  loadData()
})
</script>

Pagination with URL State

Keep pagination state in the URL:

<template>
  <div>
    <!-- Results -->
    <div class="results">
      <div v-for="item in currentPageItems" :key="item.id">
        {{ item.name }}
      </div>
    </div>

    <!-- Pagination -->
    <div class="pagination">
      <button
        @click="goToPage(currentPage - 1)"
        :disabled="currentPage <= 1"
      >
        Previous
      </button>

      <span>Page {{ currentPage }} of {{ totalPages }}</span>

      <button
        @click="goToPage(currentPage + 1)"
        :disabled="currentPage >= totalPages"
      >
        Next
      </button>
    </div>
  </div>
</template>

<script setup>
import { replaceRouteQuery, restoreRouteQuery } from 'vue-fastedgy'
import { ref, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'

const route = useRoute()
const router = useRouter()

const items = ref([])
const currentPage = ref(1)
const itemsPerPage = 10

const totalPages = computed(() => Math.ceil(items.value.length / itemsPerPage))

const currentPageItems = computed(() => {
  const start = (currentPage.value - 1) * itemsPerPage
  const end = start + itemsPerPage
  return items.value.slice(start, end)
})

onMounted(() => {
  // Restore page from URL
  currentPage.value = parseInt(restoreRouteQuery('page', route, 'pagination', '1')) || 1
  loadData()
})

const goToPage = (page) => {
  if (page < 1 || page > totalPages.value) return

  currentPage.value = page

  // Update URL
  replaceRouteQuery(router, {
    page: page > 1 ? page : undefined
  }, route, 'pagination')
}

const loadData = async () => {
  // Load data for current page
  const response = await fetch(`/api/items?page=${currentPage.value}&limit=${itemsPerPage}`)
  items.value = await response.json()
}
</script>

Multi-Step Form with URL State

Save form progress in URL for multi-step forms:

<template>
  <div class="multi-step-form">
    <!-- Step indicators -->
    <div class="steps">
      <div
        v-for="(step, index) in steps"
        :key="index"
        :class="{ active: currentStep === index, completed: index < currentStep }"
      >
        {{ step.title }}
      </div>
    </div>

    <!-- Step content -->
    <component
      :is="steps[currentStep].component"
      v-model="formData[steps[currentStep].key]"
      @next="nextStep"
      @previous="previousStep"
    />
  </div>
</template>

<script setup>
import { replaceRouteQuery, restoreRouteQuery } from 'vue-fastedgy'
import { reactive, ref, watch, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'

const route = useRoute()
const router = useRouter()

const steps = [
  { title: 'Basic Info', component: 'BasicInfoStep', key: 'basicInfo' },
  { title: 'Contact', component: 'ContactStep', key: 'contact' },
  { title: 'Preferences', component: 'PreferencesStep', key: 'preferences' },
  { title: 'Review', component: 'ReviewStep', key: 'review' }
]

const currentStep = ref(0)
const formData = reactive({
  basicInfo: {},
  contact: {},
  preferences: {},
  review: {}
})

onMounted(() => {
  // Restore step and form data from URL
  currentStep.value = parseInt(restoreRouteQuery('step', route, 'form', '0')) || 0

  const savedData = restoreRouteQuery('data', route, 'form', {}, 'object')
  if (savedData && typeof savedData === 'object') {
    Object.assign(formData, savedData)
  }
})

const nextStep = () => {
  if (currentStep.value < steps.length - 1) {
    currentStep.value++
    updateURL()
  }
}

const previousStep = () => {
  if (currentStep.value > 0) {
    currentStep.value--
    updateURL()
  }
}

const updateURL = () => {
  replaceRouteQuery(router, {
    step: currentStep.value > 0 ? currentStep.value : undefined,
    data: Object.keys(formData).some(key =>
      Object.keys(formData[key]).length > 0
    ) ? formData : undefined
  }, route, 'form')
}

// Auto-save form data when it changes
watch(formData, () => {
  updateURL()
}, { deep: true })
</script>