Routing

This section documents the current Vue 3.x release line as published at the official Vue.js documentation, which is the reference these pages are written and verified against. No specific patch version is pinned. The Composition API with <script setup> is the authoring style used throughout; the Options API is shown only as a contrast.

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, since Vue and its ecosystem iterate quickly.

This section’s bibliography lists the reference material consulted while preparing these pages.

Vue’s official client-side router is Vue Router: it maps the URL to a tree of components, keeps the address bar in sync, and provides links, params, guards, and history control. The Vue guide introduces it at Routing; everything below is in the Vue Router docs.

Creating the router

Build a router instance with createRouter, choosing a history implementation, then install it with app.use(router):

import { createRouter, createWebHistory } from 'vue-router'
import Home from './views/Home.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    { path: '/', component: Home },
    { path: '/about', component: () => import('./views/About.vue') },
  ],
})
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

createApp(App).use(router).mount('#app')

createWebHistory uses the HTML5 History API and needs a server fallback that serves index.html for unknown paths. createWebHashHistory keeps the route after a #, so it works on any static host with no server config. createMemoryHistory has no address bar and is for SSR and tests. See History modes.

<RouterView> is the outlet where the matched component renders; <RouterLink> renders an <a> that navigates without a reload and adds active classes:

<template>
  <nav>
    <RouterLink to="/">Home</RouterLink>
    <RouterLink :to="{ name: 'user', params: { id: 42 } }">Profile</RouterLink>
  </nav>
  <RouterView />
</template>

Route records

Each entry in routes is a route record: at minimum \{ path, component }. A colon marks a dynamic segment, whose value appears on route.params:

const routes = [
  { path: '/users/:id', component: UserProfile },
  { path: '/files/:pathMatch(.*)*', component: FileBrowser }, // custom regex + repeatable
]

To keep the component decoupled from the router, set props: true so params are passed as component props instead of read from useRoute(). props can also be an object (static props) or a function route ⇒ (\{ …​ }) (derive props, including from the query string):

{ path: '/users/:id', component: UserProfile, props: true },
{ path: '/search', component: Results, props: route => ({ q: route.query.q }) },

Nested routes

A record’s children render into a <RouterView> inside that record’s component, so a URL selects a chain of records and each renders into the next outlet:

const routes = [
  {
    path: '/users/:id',
    component: UserLayout,
    children: [
      { path: '', name: 'user', component: UserHome },
      { path: 'posts', component: UserPosts },
      { path: 'settings', component: UserSettings },
    ],
  },
]
A URL matched to a chain of parent and child route records, each rendering into a nested RouterView outlet

Named routes and named views

Give a record a name to navigate by identity instead of by URL string — router.push(\{ name: 'user', params: \{ id: 42 } }) — which survives path refactors. To render several outlets at one level, give each <RouterView name="…​"> a name and supply a components map (plural) on the record:

{
  path: '/dashboard',
  components: { default: DashMain, sidebar: DashSidebar },
}

Redirect, alias, and catch-all

  • redirect sends one path to another: \{ path: '/home', redirect: '/' }, or a function of the target route.

  • alias lets one record answer to several paths: \{ path: '/', component: Home, alias: '/home' }.

  • A /:pathMatch(.) record at the end matches anything unmatched — the 404 page:

{ path: '/:pathMatch(.*)*', name: 'not-found', component: NotFound }

Catch-all routes explains why the pathMatch param name matters when you navigate to it programmatically.

Programmatic navigation

useRouter() returns the instance; push, replace, or move through history:

<script setup>
import { useRouter } from 'vue-router'

const router = useRouter()

function save() {
  // ...persist...
  router.push({ name: 'user', params: { id: 42 } }) // new history entry
  // router.replace(...)  -> no new entry
  // router.go(-1)        -> like the back button
}
</script>

Navigation guards and the resolution flow

Guards can redirect (return \{ name: 'login' }), cancel (return false), or allow (return true or nothing) a navigation. They run in a fixed order (Navigation Guards):

  1. router.beforeEach — global, e.g. an auth check.

  2. beforeRouteLeave in components being left.

  3. beforeEnter on the entering route record.

  4. beforeRouteEnter in entering components (no this yet; use its callback).

  5. router.beforeResolve — global, after in-component guards and async components resolve.

  6. Navigation is confirmed; router.afterEach runs (cannot cancel — use it for analytics and titles).

router.beforeEach((to) => {
  if (to.meta.requiresAuth && !isLoggedIn()) {
    return { name: 'login', query: { redirect: to.fullPath } }
  }
})
<script setup>
import { onBeforeRouteLeave } from 'vue-router'

onBeforeRouteLeave(() => {
  if (formDirty.value) return window.confirm('Discard unsaved changes?')
})
</script>

Lazy-loaded route components

Point component at a dynamic import() so the bundler splits that view into its own chunk, fetched on first visit:

{ path: '/reports', component: () => import('./views/Reports.vue') }

See Lazy Loading Routes; it pairs well with Async Components.

Scroll behavior

With HTML5 history, supply scrollBehavior to control the viewport on navigation — reset to top, restore the saved position on back/forward, or scroll to an anchor:

const router = createRouter({
  history: createWebHistory(),
  routes,
  scrollBehavior(to, from, savedPosition) {
    if (savedPosition) return savedPosition
    if (to.hash) return { el: to.hash, behavior: 'smooth' }
    return { top: 0 }
  },
})

useRoute() and useRouter()

In <script setup>, useRoute() returns a reactive object describing the current route (params, query, hash, meta, name), and useRouter() returns the router instance for navigation. Do not destructure useRoute() — watch a getter instead so param changes on the same component are observed:

<script setup>
import { watch } from 'vue'
import { useRoute } from 'vue-router'

const route = useRoute()
watch(() => route.params.id, (id) => load(id), { immediate: true })
</script>

See also