Vue Router
Install Vue Router, build one with createRouter, and choose between history and hash mode knowing the server rule each needs.
const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes })Vue Router is the official library that maps URLs to components. Without it an application is a single screen. With it, /reports and /reports/42 render different components, the browser back button works, and a URL can be bookmarked or shared.
Installing and registering
Install it with npm install vue-router@5, or choose the router option when scaffolding with npm create vue@latest. You build a router with createRouter, which takes a history implementation and an array of routes. Each route record maps a path to a component, and usually carries a name so other code can refer to it without repeating the path. app.use(router) then registers it: that is what makes <router-view> and <router-link> available in every template, and what lets useRouter and useRoute find the router.
History mode against hash mode
createWebHistory() gives clean URLs such as /about using the browser History API. createWebHashHistory() gives /#/about; everything after the # is never sent to the server. createMemoryHistory() keeps no URL at all and is meant for server rendering and tests.
The server rule
In history mode a refresh on /about makes the browser ask the server for /about, and no such file exists. Any host serving the app must rewrite unknown paths to index.html so the router can match the path in the browser. Vite's dev server already does this, so the 404 shows up only after a deploy. Hash mode needs no server change, which is why it suits hosting you cannot configure.
Vue Router 5 also ships file-based routing in core, through the vue-router/vite plugin; it was the separate unplugin-vue-router package before. The hand-written route array shown here is unchanged from version 4.
Example
<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router'
</script>
<template>
<header>
<nav>
<!-- kebab-case and PascalCase are the same component -->
<router-link to="/">Home</router-link>
<RouterLink to="/about">About</RouterLink>
</nav>
</header>
<main>
<router-view />
</main>
</template>
<style scoped>
nav {
display: flex;
gap: 1rem;
}
main {
padding: 1rem;
}
</style>When to use it
- Give a dashboard separate, bookmarkable URLs for its overview, settings and billing screens
- Diagnose a Vue app that works in development but 404s when a deep link is refreshed in production
- Choose hash mode for an app shipped inside a host you cannot add rewrite rules to
More examples
The router module
The conventional place for routes is src/router/index.ts, exporting the router as the default. HomeView is imported eagerly because it is the first screen most visitors see; the others are functions returning an import, so their code is fetched on demand. The last record is a catch-all: without it, an unknown URL matches nothing and router-view renders empty. createWebHistory takes an optional base path, and import.meta.env.BASE_URL is Vite's value for the base the app was built with, so the same code works when the app is served from a subdirectory.
// src/router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{ path: '/', name: 'home', component: HomeView },
{
path: '/about',
name: 'about',
component: () => import('../views/AboutView.vue'),
},
{
path: '/:pathMatch(.*)*',
name: 'not-found',
component: () => import('../views/NotFoundView.vue'),
},
],
})
export default routerRegistering the router on the app
createRouter only builds the router; app.use(router) installs it. Install it before mount, or the first render happens with no router and router-link throws. If you need the initial URL resolved before the app is visible, await router.isReady() between use and mount.
// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')The rewrite rule history mode needs
try_files serves a real file if one matches the request, and otherwise falls back to index.html so the router can resolve the path in the browser. Every static host has an equivalent: a rewrite rule, a 200 redirect, or a single-page-app setting. If you cannot change the server at all, switch to createWebHashHistory, where the path after the hash never reaches it.
# deploy/nginx.conf
server {
listen 80;
root /usr/share/nginx/html;
location / {
try_files $uri $uri/ /index.html;
}
}
Discussion