Getting started using Vue's Vue-router for single page apps

Posted on April 01, 2016 | By Matt Stauffer

Warning: This post is over a year old. I don't always update old posts with new information, so some of this information may be out of date.

One of the things I love most about Vue.js is that it works on the simplest of pages with the simplest of components. You don't need any complicated build tools or dependency trees.

However, Vue isn't just limited to simple components. Vue-resource makes AJAX easy, vue-router sets up single-page-app routing with almost no effort, and one day I'll learn Vuex, I promise.

I want to show you just how easy it is to use vue-router to create a single-page-app using Vue. And trust me: it is easy. If you've already created your first component using Vue, you're 90% of the way there.

Getting set up

As is often the case, I choose Laravel Elixir as my build tool.

You can use any build system that gives you access to your NPM packages, or you can even pull in the package manually (via CDN) or via Bower. In the end, get the vue-router package installed somehow. If you're not going to use Elixir, just skip the next section.

Installing Vue and vue-router with Laravel Elixir (Optional)

If you are going to start a new project using Laravel Elixir, read the docs for basic installation instructions. Then add vue and vue-router:

npm install vue vue-router --save

Next, you'll want to add a Browserify task for your JavaScript file:

// gulpfile.js
elixir(function (mix) {
    mix.browserify('app.js');
});

This now expects that you have a file at resources/assets/js/app.js and will pass that file through Browserify and Babel and output it at public/js/app.js. Create the resources version of the file, and then run gulp watch to get it running.

Pulling in the dependencies

In your primary JavaScript file, you'll want to pull in Vue and vue-router using whatever import system you have available:

var Vue = require('vue');
var VueRouter = require('vue-router');

Vue.use(VueRouter);

We pull in Vue and vue-router, and then we link them together. Now, let's write our app.

Creating your application

Like in any Vue app, we need to create a base application. But unlike in other Vue apps, the core templating work we'll be doing is mapping certain routes to certain components. There's no new Vue concept like a "page"--each page is just a component, which may contain other components.

So, let's create our App and our router:

var App = Vue.extend({});

var router = new VueRouter();

router.start(App, '#app');

That's it! This won't actually do anything, since we haven't mapped any routes, but we just defined an App, defined a router, bound them together, and then initialized the router.

Mapping routes to components

Now that we have an app and a router, let's define a few routes:

var App = Vue.extend({});

var router = new VueRouter();

var Home = Vue.extend({
  template: 'Welcome to the <b>home page</b>!';
});

var People = Vue.extend({
  template: 'Look at all the people who work here!';
});

router.map({
  '/': {
    component: Home
  },
  '/people': {
    component: People
  }
});

router.start(App, '#app');

We've now defined two possible routes in our application, and mapped each with a component.

Building the HTML page

Let's create an HTML page to hold our router. Despite what you might think, this page doesn't need to be entirely empty. With vue-router, your app can contain some code that isn't switched out by components--for example, a nav section.

...
<div id="app">
  <a v-link="{ path: '/' }"><h1>Our company</h1></a>
  <ul class="navigation">
    <li><a v-link="{ path: '/people' }">People</a></li>
  </ul>

  <router-view></router-view>
</div>
...

Let's assume this page has HTML header and wrapper tags, and also imports our scripts and dependencies in the header or footer somewhere.

We can see a few important pieces here. First, when we start our router in our script file, we bound it to '#app', so we needed to create a page element with the ID of "app" to bind our Vue app to.

Second, you can see the Vue syntax for links: using the v-link property and passing it a JSON object. For now, we'll stick to { path: '/url-here' }.

If the link you're currently visiting is "active"--meaning the user has that link open--the router will apply the class v-link-active to that link, which you can then style uniquely. You can also change that class or have it applied to a separate related element like a parent div or li--check out the docs to learn more.

Finally, we have a <router-view> component, which is where the output of each page's component will go.

If you open that page up and everything loads correctly, you now have your first single-page app using vue-router!

You might notice that it's currently using "hashbang"-style navigation, where all of your routes are appended after #!. You can disable this, which we'll talk about later, but it will take a bit of server mumbo jumbo, just so you're prepared.

More advanced route definition

We defined some pretty simple routes above. Let's dig a little further into the sorts of routes you can define with vue-router.

Route parameters

You're probably going to want to define routes at some point that are more than just a static URL. We call URLs that can match multiple URLS "dynamic" URLs. Let's learn how to define one.

router.map({
  '/people/:personId': {
    component: {
      template: 'Person ID is {{$route.params.personId}}'     
    }
  }
});

We can learn a few things from this example. First, we can see that vue-router maps dynamic parameters using this syntax: :paramName.

Second, we can see that vue-router allows you to define component objects inline, if you'd like.

And third, we can see we have access to a $route object with a params property that's an object of all of the matched dynamic URL parameters.

Greedy route segments

If you want to define a dynamic route segment that can match multiple segments, use a segment definition name that starts with * instead of ::

router.map({
  '/people/*greedy' => {},
  '/people/*greedy/baz' => {},
});

That first route definition will match /people/a, /people/a/b, /people/a/b/c/d/e/f.

The second route definition will match /people/a/baz, /people/a/b/c/d/e/f/g/baz, etc.

And both will return a greedy segment on the route: $route.params.greedy, which is equal to the full string for that segment. /people/a/b/baz would return { greedy: 'a/b' }.

Named routes

If you've used Laravel routes before, you'll be familiar with the idea of giving any given route a "name" that you can use to refer to it later. You can do this with vue-router as well:

router.map({
  '/people/:personId': {
    name: 'people.show',
    component: People
  }
});

You can then link to that route with v-link:

<a v-link="{ name: 'people.show', params: { personId: 5 }}">Person 5</a>

Navigating to a route in JavaScript

We've seen how v-link can replace normal links, but what if you need to trigger a navigation event in your JavaScript? router.go() is your friend:

router.go({ name: 'people.show', params : { personId: 5 }});

You can also use router.replace(), which functions the same except it doesn't generate a new history record in the browser.

Router options

When you instantiate the router object at the top of your JavaScript file, you can optionally pass it a few configuration properties.

  • hashbang: If you set this to false, the system will use actual URLs (/people) instead of hashbang URLs (/#!/people). If you do this, you'll want to enable history and configure your server correctly.
  • history: Uses your browser's push state to update your browser history as you navigate through the site. Requires your server to be configured correctly
  • root: If your application is not at your web root, set this property for navigation to correctly append the subdirectory your app is in. For example, if your app is in /app, set this property to /app so that vue-router URLs are generated appropriately; /app/people instead of just /people.

If you're using this app within a Laravel app, instead of configuring nginx or Apache to handle hashbang-less push state, you could configure your Laravel app to handle it; just set up a capture route that grabs all valid URLs and passes them the view that's outputting your Vue code.

 Route::get('/{vue_capture?}', function () {
   return view('home');
 })->where('vue_capture', '[\/\w\.-]*');

Getting route information inside your components

Every component in your app will have access to this.$route, which is a "Route Context Object" and exposes properties that can be useful for getting information about your route. Your templates will also have access to this object as $route.

  • $route.path is equal to the absolute path; e.g. /people
  • $route.params contains the key/value pairs of your dynamic sections; e.g. { personId: 5 }
  • $route.query contains the key/value pairs of your query string; e.g. /people?sortBy=names would return { sortBy : lastName }
  • $route.router returns the vue-router instance
  • $route.matched returns route configuration objects for every matched segment in the current route
  • $route.name returns the name, if it has one, of the current route

You can also pass custom data into your components by passing it into the route definition. For example, I wanted a certain section of my app to be admin-only, so I passed that as a flag into the definition, and was about to check it inside my component as $route.adminOnly.

router.map({
  '/secret-admin-panels': {
    component: SecretDashboard,
    adminOnly: true
  }
});

Setting hooks

It was possible for me to check the $route.adminOnly property in my components, but that's something that would be better handled before users are even granted access to the component, using something like middleware. With vue-router, that's going to be route hooks, like beforeEach:

// Sample means of checking user access
var MyUser = { admin: false };

router.beforeEach(function (transition) {
  if (transition.to.adminOnly && ! MyUser.admin) {
    transition.redirect('/login');
  } else {  
    transition.next();
  }
});

As you can see, we intercept the request before the component is rendered. We can allow it to move forward using transition.next(), but we can also intercept the user and redirect or abort transition.abort().

There's also an afterEach() hook.

Miscellaneous

A few quick notes before we're done.

First, <router-view> can be passed props just like any other on-page component.

Second, Vue has an entire Transition Pipeline that you can use to define visual and functional transition behavior between pages.

Finally, this article isn't exhaustive; make sure to read the docs to learn more.

Concluding

That's it for now. You can see how simple it is to set up your first single-page app using Vue and vue-router, and how smoothly the transition to single component to single-page app can be.

If you want to see a (somewhat) working example, my work-in-progress learning app Suggestive uses vue-router.


Comments? I'm @stauffermatt on Twitter


Tags: vuejs  •  vue-router