Router Internals

Preview — 3 of 10 questions

How would you implement a custom history mode for Vue Router?

javascript
import type { RouterHistory } from 'vue-router'

function createCustomHistory(base = ''): RouterHistory {
  let listeners: Array<(location: string) => void> = []
  let currentLocation = '/'

  return {
    base,
    get location() { return currentLocation },
    get state() { return window.history.state },

    push(to, data) {
      currentLocation = to
      window.history.pushState(data, '', base + to)
      listeners.forEach(l => l(to))
    },

    replace(to, data) {
      currentLocation = to
      window.history.replaceState(data, '', base + to)
      listeners.forEach(l => l(to))
    },

    go(delta, triggerListeners = true) {
      window.history.go(delta)
    },

    listen(callback) {
      listeners.push(callback)
      return () => {
        listeners = listeners.filter(l => l !== callback)
      }
    },

    createHref(location) {
      return base + location
    },

    destroy() {
      listeners = []
    }
  }
}

// Usage
const router = createRouter({
  history: createCustomHistory('/app'),
  routes
})
APass a customHistory object to createRouter with push, replace, and go methods
BExtend createWebHistory with additional methods via JavaScript class inheritance
CImplement the RouterHistory interface with base, location, state, push, replace, go, listen, and destroy methods using createCustomHistory
DUse router.beforeEach to intercept and override the default history behavior

How does Vue Router's Composition API handle SSR where window is unavailable?

javascript
// app.ts — isomorphic app factory
import { createApp } from 'vue'
import { createRouter, createWebHistory, createMemoryHistory } from 'vue-router'
import routes from './routes'
import App from './App.vue'

export function createSSRApp() {
  const isServer = typeof window === 'undefined'

  const router = createRouter({
    history: isServer ? createMemoryHistory() : createWebHistory(),
    routes
  })

  const app = createApp(App)
  app.use(router)
  return { app, router }
}

// server-entry.ts
export async function render(url: string) {
  const { app, router } = createSSRApp()

  // Push the requested URL
  await router.push(url)
  await router.isReady() // Wait for async guards

  // Check for redirects triggered by guards
  const currentRoute = router.currentRoute.value
  if (currentRoute.fullPath !== url) {
    // Navigation guard redirected
    return { redirect: currentRoute.fullPath }
  }

  const html = await renderToString(app)
  return { html }
}

// client-entry.ts
const { app, router } = createSSRApp()
await router.isReady() // Ensure same route as SSR before hydration
app.mount('#app')
AIt automatically detects SSR and uses a no-op implementation for browser APIs
BcreateMemoryHistory() is used on the server; the router.push(url) call sets the initial route before rendering; createWebHistory() is used on the client
CVue Router is not usable in SSR — use Nuxt for server-side routing
DBoth createWebHistory and createWebHashHistory work transparently in SSR

How do you create reusable navigation guard factories for complex authorization?

javascript
import type { NavigationGuardWithThis, RouteLocationNormalized } from 'vue-router'

// Guard factories
function requireAuth(): NavigationGuardWithThis<undefined> {
  return async (to, from) => {
    const { useAuthStore } = await import('@/stores/auth')
    const auth = useAuthStore()

    if (!auth.isAuthenticated) {
      return {
        name: 'login',
        query: { redirect: to.fullPath }
      }
    }
  }
}

function requireRole(...roles: string[]): NavigationGuardWithThis<undefined> {
  return async (to, from) => {
    const { useAuthStore } = await import('@/stores/auth')
    const auth = useAuthStore()

    if (!roles.some(role => auth.user?.roles.includes(role))) {
      return { name: 'forbidden' }
    }
  }
}

function requireFeatureFlag(flag: string): NavigationGuardWithThis<undefined> {
  return (to) => {
    if (!featureFlags.isEnabled(flag)) {
      return { name: 'not-found' }
    }
  }
}

// Compose in routes — guards run in array order
const routes = [
  {
    path: '/admin',
    component: AdminView,
    beforeEnter: [requireAuth(), requireRole('admin', 'superuser')]
  },
  {
    path: '/beta-feature',
    component: BetaView,
    beforeEnter: [requireAuth(), requireFeatureFlag('beta-feature')]
  }
]
AUse beforeEach with a long if/else chain inside it
BCreate guard factory functions that return guard functions, composing them in route beforeEnter arrays
CUse a global Pinia store to manage all navigation permissions
DTypeScript interfaces are the only way to enforce guard composition

Sign up free to play

Answer all 10 questions (7 more), see explanations for every answer, and track your score.