Vue.js 3 Router Setup & Basics Tutorial

In this Vue tutorial we learn how to navigate to different pages in our app with routing.

We cover how to install the router package, create and load routes to components and views, dynamic route links and redirecting and 404 routes.

Finally, we take a look at how to navigate with programmatically and how to lazy load routes to improve application performance.

Lesson Video

If you prefer to learn visually, you can watch this lesson in video format.

Lesson Project

If you want to follow along with the examples in this lesson, you will need an app generated by the Vue CLI . If you already have one from a previous lesson, you can use it instead.

We can set up the Routing package in multiple ways.

  • Add it to an existing project by manually installing it with npm.
  • Generate a new project and add the Routing package. We cover this process at the end of this lesson .

What is Routing?

Routing is how we load components when the user navigates to a “page” in our application.

As an example, let’s say we have a Vue app at www.example.com with links to various “pages” like Contact Us or About Us. When the user clicks on a link, it loads the appropriate component.

But when we load a component, the url stays www.example.com . What we want is for the url to reflect which component is loaded, which “page” the user is on, like www.example.com/contact .

With routing, we associate the url with a component so that the app simulates navigation to a different page.

How to manually install and set up the Routing package

To manually install and set up the Routing package, we follow a simple 3 step process.

  • Step 1: Install the router
  • Step 2: Structure the project
  • Step 3: Create the router

Step 1: Install the Routing package with npm

To install the package manually, open a new terminal/command prompt and navigate to your project folder, then run the following command.

tip If you’re working in VSCode, you can go to Terminal > New Terminal to open a terminal or command prompt with the project folder already selected.

Command: npm install Router
npm install vue-router@next --save

tip Application dependencies use the --save flag on the command, and developer dependencies use the --save-dev flag. When you install the Routing Package, ensure you always use the --save flag on the command.

Once the package has been installed, we should see it in the package.json file under “dependencies”.

Step 2: Structure the project

Although it’s not strictly necessary, we want to stay organized and create the following folders in the /src/ of our project.

  • /components/

    This is where we store the components that are included in our “pages”.

  • /router/

    This is where we will create the router and set all the routes for the application.

  • /views/

    This is where we store our “pages”, like Home or About. These “pages” are just the components that will hold other components, like a SearchBar, from the /components/ folder.

Example: project
project_folder/
├── src/
|   ├── components/
|   ├── router/
|   ├── views/
|   └── App.vue

The folder names can be anything you want, but we use the Vue convention here. The same folders are generated when we generate a new project with the Vue CLI that includes the router.

Step 3: Create the router

We’ll start by creating a new file in /src/routes/index.js . We’ll create all our routes in this file and then import it into main.js to be registered.

Example: project
project_folder/
├── src/
|   ├── components/
|   ├── router/
|   |   └── index.js
|   ├── views/
|   └── App.vue

In the index.js file, we need to import two methods from the ‘vue-router’ package.

  • createRouter

    This is the method that creates the router. We can think of it as the createApp() method, but for the routing system.

  • createWebHistory

    This method allows Vue to tap into the browser’s forward and back button functionality. It also disables the # symbol in the url.

Example: src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'

The next step is to actually create the router.

The createRouter method takes a configuration object as an argument with at least two options.

  • history

    This is where we specify the createWebHistory method.

  • routes

    This is an array that contains the routes as objects. We’ll take a look at these routes in a bit.

Example: src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: []
})

We don’t have any routes yet, so our array can be empty for the moment.

The next step is to export the router we just created.

Example: src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  history: createWebHistory(),
  routes: []
})

export default router

The final step is to import this router into the main.js file and register it to the app with the use method.

Example: src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)

// use router
app.use(router)
app.mount('#app')

note Ensure the router is registered before the app is mounted, otherwise it won’t work.

That’s it, we’re ready to start creating routes.

How to create a route to a component

The standard practice in Vue is to define components as views that act as “pages” in our app. They will import all the components they need to act as a “page”.

As an example, we’ll create two new components in /src/views/ called HomePage.vue and UsersPage.vue .

Example: project
project_folder/
├── src/
|   ├── components/
|   ├── router/
|   |   └── index.js
|   ├── views/
|   |   ├── HomePage.vue
|   |   └── UsersPage.vue
|   └── App.vue

Each will just have a template block with a heading to help us identify them.

Example: src/views/HomePage.vue
<template>
  <h2>Home page</h2>
</template>
Example: src/views/UsersPage.vue
<template>
  <h2>Users page</h2>
</template>

Now let’s head over to /src/router/index.js and create the routes for them in the routes array.

Each route is an object with at least two options.

  • path

    This is the relative path the (view) component can be accessed with.

    For example: If we specify the path as /contact then it will translate into the URL as www.example.com/contact .

    note The path is relative to your base url, not the internal folder structure.

  • component

    This is the (view) component we want to load when the route url is accessed. To have access to the components, they must be imported at the top of the document.

Let’s add two routes for our HomePage and UsersPage components.

  • We want the HomePage component to load when the user visits the application’s main page, like www.example.com . In that case we specify the path as “/”.
  • We want the UsersPage component on the “/user” path to demonstrate that the path can be different from the component, and because we will add parameters to it later on.
Example: src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'

import HomePage from '../views/HomePage'
import UsersPage from '../views/UsersPage'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      component: HomePage
    },
    {
      path: '/user',
      component: UsersPage
    }
  ]
})

export default router

How to display views (load router components)

Now that we have some routes set up, we need to tell Vue where to render the views.

Our first instinct might be to load the components in the root App, then use v-if directives on them. But Vue makes it much easier.

Vue has a special component called the router-view that will handle the entire process for us. All we need to do is include it once in the template block where we want the views to be rendered.

Syntax: router-view
<template>
  <router-view/>
</template>

As an example, let’s add the router-view to our root App component.

Example: /src/App.vue
<template>
  <router-view/>
</template>

Notice that we don’t import the individual views in the config object, in fact we don’t have a config object at all. We don’t need to import them here because they’re already imported in the route system in src/router/index.js .

The router will handle the loading and unloading of the views behind the scenes, we don’t have to do it manually like with regular components.

If we save the files and take a look in the browser, we should see a heading with the text “Home Page”. That means our HomePage component was loaded successfully.

And if we go to http://localhost:8080/user in the browser, it will show the UsersPage component.

How to link to routes

Vue gives us another custom component called router-link that we use to link to routes, instead of using regular anchor tags.

The router-link component is an open-and-close element. The link’s anchor text is specified between the tags and it uses the to prop to specify the route we want to link to.

Syntax: router-link
<router-link to="/relative_path">Link Text</router-link>

To demonstrate, let’s add two router links to the HomePage and UsersPage views in our example.

Example: src/App.vue
<template>
  <div>
    <router-link to="/">Home</router-link> |
    <router-link to="/user">Users</router-link>
  </div>

  <router-view/>
</template>

If we save the file and take a look in the browser, we should see the two links in the top left. Clicking on a link changes the route in the URL bar and displays the correct component.

How to style an active router link

When we compile the application, Vue will convert the router-link to a standard anchor tag.

Example: router-link converts to anchor tag
// Vue router link
<router-link to="/user">Users</router-link>

// anchor link
<a href="/user">Users</a>

If the route in the URL matches a link, Vue adds two classes to that link.

  • router-link-active

    The link will have this class applied as long as the current path starts with the route. Any nested paths will also be affected.

    For example: If the path is /user , both www.example.com/user and www.example.com/user/john will be affected.

  • router-link-exact-active

    The link will have this class applied only when the route matches the patch exactly.

    For example: If the path is /user , only www.example.com/user will be affected, not www.example.com/user/john .

To demonstrate, let’s add a style that shows the active link color as red.

Example: src/App.vue
<template>
  <div>
    <router-link to="/">Home</router-link> |
    <router-link to="/user">Users</router-link>
  </div>
  <router-view/>
</template>

<style>
.router-link-exact-active {
  color: red
}
</style>

If we cycle through the routes in the browser, the link will turn red each time the route matches it.

Dynamic route links

At the moment, our links are hardcoded. If we want to change a route some time in the future, we’ll have to find all its links and update them manually.

For example, we might decide we want the “/contact” route to be “/contact-us”. Then we would have to go to every navigation menu in our application and update the router-link .

Vue allows us to specify a name option when we define a route.

Syntax: named route
{
  path: '/',
  name: 'Home',
  component: HomePage
}

We can then reference that name in an object in the router link and Vue will automatically connect it to the corresponding path.

Syntax: named link
<router-link :to="{ name: 'Home' }">Home</router-link>

To demonstrate, let’s define some names in our router system in /src/router/index.js .

Example: src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'

import HomePage from '../views/HomePage'
import UsersPage from '../views/UsersPage'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      name: 'Home',
      component: HomePage
    },
    {
      path: '/user',
      name: 'Users',
      component: UsersPage
    }
  ]
})

export default router

Then link them by their names in the root App component.

Example: src/App.vue
<template>
  <div>
    <router-link :to="{ name: 'Home'  }">Home</router-link> |
    <router-link :to="{ name: 'Users' }">Users</router-link>
  </div>

  <router-view/>
</template>

When we save the files and click on the links in the browser, everything still works. Vue converted the names to their corresponding routes.

Now when we need to change a route, we only have to change it in the routing system in /src/router/index.js , decreasing the risk of breaking things.

How to redirect one route to another

Vue allows us to redirect one route to another with the redirect option in the route we want to redirect from.

Syntax: redirect
{
  path: '/old-route',
  redirect: '/new-route'
}

For example, let’s say that the original route to the UsersPage view was /member , but we decide to change /member to /user .

Example: src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'

import HomePage from '../views/HomePage'
import UsersPage from '../views/UsersPage'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      name: 'Home',
      component: HomePage
    },
    {
      path: '/user',
      name: 'Users',
      component: UsersPage
    },
    {
      path: '/member',
      redirect: '/user'
    }
  ]
})

export default router

If we run the example in the browser and go to http://localhost:8080/member , it will redirect us to http://localhost:8080/user .

A route must exist if we want to redirect to it. Let’s say we have it the other way around. Our old route is /user and we want to redirect to /member .

Example: src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'

import HomePage from '../views/HomePage'
import UsersPage from '../views/UsersPage'
import UserSingle from '../views/UserSingle'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      name: 'Home',
      component: HomePage
    },
    {
      path: '/user',
      name: 'Users',
      component: UsersPage
    },
    {
      path: '/user',
      redirect: '/member'
    }
  ]
})

export default router

If we go to http://localhost:8080/user , it’s supposed to redirect us to http://localhost:8080/member , but it doesn’t.

That’s because the /member path isn’t associated with a component. So let’s change the Users route to have the /member path.

Example: src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'

import HomePage from '../views/HomePage'
import UsersPage from '../views/UsersPage'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      name: 'Home',
      component: HomePage
    },
    {
      path: '/member',
      name: 'Users',
      component: UsersPage
    },
    {
      path: '/user',
      redirect: '/member'
    }
  ]
})

export default router

Now the Users route is associated with the /member path so it works.

Catch-all routes and 404 pages

Similar to regular websites, it’s possible for a user to end up on a page that doesn’t exist, a sort of soft 404. It might happen because we made a mistake with an internal link or redirect, or the user just typed the wrong address in the url bar.

Regardless of how it happens, we want to handle it elegantly and give the user a way to find what they were looking for. Vue makes it easy for us to do with a regular expression (Regex) that catches all the urls that isn’t defined in the route system.

If any of the links match, we can tell Vue to load a view that tells the user what’s going on and gives them options on how to proceed.

Syntax: catchAll
{
  path: '/:catchAll(.*)',
  name: '404Name',
  component: 404View
}

The name and component options can be anything we want but the path must be this catchAll(.*) route property.

note The catch-all route must be the last route in the array, otherwise it will catch any routes defined below it.

Let’s demonstrate by implementing it in our example.

We will start by creating a 404 view in /src/views/NotFound.vue . We’ll keep it simple and just define a heading with the text “404 Page Not Found” in the template block.

Example: src/views/NotFound.vue
<template>
  <h2>404 Page Not Found</h2>
</template>

Then, we’ll head over to the index.js file and add the catch-all Regex.

Example: src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'

import HomePage from '../views/HomePage'
import UsersPage from '../views/UsersPage'
import NotFound from '../views/NotFound'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      name: 'Home',
      component: HomePage
    },
    {
      path: '/user',
      name: 'Users',
      component: UsersPage
    },
    {
      path: '/member',
      redirect: '/user'
    },
    {
      path: '/:catchAll(.*)',
      name: 'NotFound',
      component: NotFound
    }
  ]
})

export default router

If we point the browser to http://localhost:8080/any-route-that-doesnt-exist , it will load the NotFound view.

The History API and programmatic navigation

When we created the router earlier in the lesson, we used an option called history with the createWebHistory method as its value. This method does a few things, including allowing us to use the browser’s forward and back functionality.

We can use that functionality to navigate around our application programmatically with the $router instance object and two of its methods.

  • go

    This method allows us to specify a positive or negative number to move forward or backward in the history.

  • push

    This method allows us to move to a specific path. It takes a hardcoded path or object with a path name as argument.

note The $router instance object only allows us to access the web history API, it is not the same as the $route instance object, which let’s us access route parameters.

Syntax: $router instance object
// go to the specified route
$router.push('/hardcoded-route-path')
// or
$router.push({ name: '/RouteName' })


// go forward 1 step (if possible)
$router.go(1)
// go back 1 step (if possible)
$router.go(-1)

To demonstrate, let’s add these 3 methods to our root App component.

Example: src/App.vue
<template>
  <div>
    <router-link :to="{ name: 'Home' }">Home</router-link> |
    <router-link :to="{ name: 'Users' }">Users</router-link>
  </div>

  <p>
  <button @click="$router.go(-1)">Go back 1 step</button>
  <button @click="$router.go(1)" >Go forward 1 step</button>

  <button @click="$router.push('/user/1')">Redirect to "/user/1"</button>
  </p>

  <router-view/>
</template>

The go method requires there to be at least one step in the history, so we will have to navigate around the app with the router-links before it will work correctly.

The History API is especially useful when dealing with multi-step forms. If a user missed something in one of the steps, they can be sent back easily.

Lazy loading views

In a real world application, we would try to make our Javascript bundle size as small as possible so the user doesn’t have to wait too long.

We can do something called Code Splitting, which means we only load a view’s javascript when a user visits that route.

Instead of importing a component and declaring it in the component option, we create an arrow function that directly imports the component.

Syntax: import view
{
  path: '/route-path',
  name: 'RouteName',
  component: () => import('../views/RouteView.vue')
}

Let’s demonstrate by lazy loading our Users View.

Example: src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'

import HomePage from '../views/HomePage'
// import UsersPage from '../views/UsersPage'
import NotFound from '../views/NotFound'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      path: '/',
      name: 'Home',
      component: HomePage
    },
    {
      path: '/user',
      name: 'Users',
      component: () => import('../views/UsersPage')
    },
    {
      path: '/member',
      redirect: '/user'
    },
    {
      path: '/:catchAll(.*)',
      name: 'NotFound',
      component: NotFound
    }
  ]
})

export default router

If we go to the browser and click on the “Users” link, the app will still load the view as expected so we don’t really see a difference right now.

The difference will be in the size of the Javascript bundle once the application is compiled in the build step.

How to scaffold a new project with the Router package

The Vue CLI allows us to add the Routing Package when we scaffold a new project.

We want to Manually select features.

Example: manual selection
? Please pick a preset:
  Default ([Vue 2] babel, eslint)
  Default (Vue 3) ([Vue 3] babel, eslint)
> Manually select features

Then add the Router.

Example:
? Check the features needed for your project:
 (*) Choose Vue version
 (*) Babel
 ( ) TypeScript
 ( ) Progressive Web App (PWA) Support
>(*) Router
 ( ) Vuex
 ( ) CSS Pre-processors
 (*) Linter / Formatter
 ( ) Unit Testing
 ( ) E2E Testing

Choose Y to add history mode for Vue to add the History API .

Example:
? Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n)

The CLI will set up a few routes and register the router in the application’s config.

Further Reading

For more information on the topics covered in this lesson, please see the relevant section below.