Your first router
Give your app real pages with Vue Router: a routes list, a history mode, RouterView to show the match and RouterLink to move between pages without a reload.
What you'll learn
When you finish, you can do these.- 1Explain how a link changes the page without a reload
- 2Create a router from a list of routes and install it
- 3Show pages with RouterView and link them with RouterLink
- 4Spot and fix a plain link and a missing RouterView
Picture a small website with two pages, Home and About. Built with Vue, it is a single-page app: the browser loads one HTML file once, then Vue changes what is on screen. Visitors still expect real pages: an address for each, a back button that works, links they can share. Vue Router, Vue's official library for pages, gives you that. It reads the address and swaps in the matching component, with no new page from the server.
The Code tab holds that site: the two pages in pages/, the router in router.ts, and App.vue, which wraps every page with a menu and a button that counts your clicks. By the end you add a Contact page and can say why the count survives every page change. You need one idea from Events and v-model, in the Beginner track: a child component is one used inside another, and data declared in the parent, here App.vue, belongs to the parent, not to the pages shown inside it.
What happens when you click a link
Before any code, here is what happens on a click. The router keeps a list of routes. A route pairs a path, the part of the address after the site's name, with the component to show for it: the path /about with the About page, pages/About.vue in the Code tab. When the address changes, the router looks through the list, finds the route whose path matches, and renders its component in the spot you mark with <RouterView />.
- URL/about
- routespath to component
- matched componentAbout.vue
- RouterViewAbout renders here
A <RouterLink to="/about">, the router's link component, renders an ordinary <a> element but catches the click: it changes the address itself, with the browser's History API, and tells the router. No new page loads.
Everything outside RouterView stays on the page, data included. In the Code tab's preview, click the button a few times, open About, come back: the count has not moved.
Create the router and install it
Setting up the router takes two steps: create it, then install it.
First, router.ts lists the routes and creates the router with createRouter(), a function from the vue-router package.
import { createRouter, createMemoryHistory } from 'vue-router'
import Home from './pages/Home.vue'
import About from './pages/About.vue'
export const router = createRouter({
history: createMemoryHistory(),
routes: [
{ path: '/', name: 'home', component: Home },
{ path: '/about', name: 'about', component: About },
],
})
Each route has a path, the component to show and an optional name.
The history option decides where the address lives. In your own project you write createWebHistory(), which gives clean addresses like /about in the browser's address bar. The Code tab's preview has no address bar, so the starter uses createMemoryHistory(), which keeps the history in memory, as tests do.
Second, main.ts, the file that starts the app, imports the router that router.ts exports, then installs it in one line:
createApp(App).use(router).mount('#app')
.use(router) makes RouterLink and RouterView usable in every template, gives components the current route, and shows the page for the starting address. It comes before .mount('#app'), which puts the app on the page, so all of that is ready for the first render.
RouterView, RouterLink and the active link
The template of App.vue holds the menu of links and the outlet where pages appear:
<template>
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
</nav>
<p class="address">Address: {{ $route.fullPath }}</p>
<button @click="clicks++">Clicked {{ clicks }} times</button>
<main>
<RouterView />
</main>
</template>
<RouterView /> is the outlet. Whichever route matches, its component renders there, inside App. Only that page changes: the button and clicks, the ref it counts in, belong to App, the parent, so they stay put.
Under the menu, the address line prints $route.fullPath, the path you are on, since the preview has no address bar. $route is the current route, available in every template once the router is installed.
Each RouterLink's to takes a path, or a route by name: :to="{ name: 'about' }". The colon makes the value JavaScript, an object with the route's name. A link by name keeps working when you change the path later, because the router looks the path up for you.
RouterLink also marks the link to the page you are on: it adds the class router-link-active, which the style block of App.vue makes bold. On /about, Home is not bold, although /about starts with /: the router compares routes, not the first characters of the path.
A second class, router-link-exact-active, goes only on the link to exactly the current route. The two differ once routes sit inside a parent route (Nested routes and layouts, later in this track): router-link-active then marks the parent's link too. With flat routes like these, both land on the same link.
What goes wrong
Three mistakes break a first router. The first, a plain link, reloads everything.
<a href="/about">About</a>
The browser asks the server for /about, throws the running app away and starts it again, so the count is back to 0. Use RouterLink for every link inside your app.
<RouterLink to="/about">About</RouterLink>
About opens under the menu, and the count stays.
The second mistake, a missing RouterView, fails in silence.
<main>
<h2>Pages go here</h2>
</main>
The links still work: the address line changes and the bold link moves. No page appears and no warning shows, because the router matched a route and found nowhere to render it.
<main>
<RouterView />
</main>
Each page appears under the menu again.
The third mistake, forgetting to install the router, breaks every template that uses it.
createApp(App).mount('#app')
Nothing registered RouterLink or RouterView, so Vue warns Failed to resolve component: RouterLink and the same for RouterView, and the links render as plain text. This starter also reads $route, which does not exist without the router, so the preview shows Cannot read properties of undefined (reading 'fullPath') and App renders nothing.
createApp(App).use(router).mount('#app')
The menu, the address line and the page come back.
What to remember
- A route pairs a path with a component. The router matches the address against the list and renders the match inside
<RouterView />. With no RouterView, no page shows and nothing warns. <RouterLink>changes the address without a reload, so data outside RouterView survives. A plain<a href>reloads the whole app.app.use(router)beforemount()registers RouterLink and RouterView and starts the first navigation.- Real projects use
createWebHistory()for clean addresses.createMemoryHistory()is for places with no address bar, such as this preview and tests. router-link-activemarks the link to the current route or a parent of it, androuter-link-exact-activemarks only the exact one.
Try it
Open the Code tab and change the starter code. Each task says what you should see.- Give Contact a route. In router.ts, import
Contactfrom./pages/Contact.vueand add{ path: '/contact', name: 'contact', component: Contact }. In App.vue, add<RouterLink to="/contact">Contact</RouterLink>to the menu. Click it: the Contact page shows, the address line reads /contact, Contact turns bold, and the count is still where you left it. - Link by name. Change the About link to
:to="{ name: 'about' }", then change the about route's path to/about-us. Click About: the page still opens, and the address line reads /about-us. Change the link back toto="/about"and click it: no route has that path now, so no page appears and the preview warnsNo match found for location with path "/about". - Replace the About RouterLink with
<a href="/about">About</a>and click it. The preview sends plain links to a new browser tab, which opens on a Page not found while the preview has not moved. That new tab is a full page load, which is what a plain link does to a real app. Close the tab and put the RouterLink back.