vue js single page application

How to build a single-page application with Vue.js

Vue SPA

Single-page applications (SPA) have many advantages such as speed, really good UX, and full control over the markup if you develop with Drupal. More and more sites using SPA appear; more and more tools that simplify the process of developing complex SPA are designed.

Vue.js is a young reactive JavaScript framework created by a former Google employee Evan You. Following this guide, we will create a client-side application for a simple Vue blog. This application will display a list of all blog posts and the full text of the post next to it. All changes will occur without reloading the page.

Through this Vue SPA example, you will learn how to fetch data in Vue, how to create a router, and what Vue components are.

Vue Single Page App

→ Server side

In this Vue.js tutorial, we will only talk about writing a client with Vue. We will not cover creating a REST server. So I will use the jsonplaceholder.typicode.com service to provide the fake online REST API. Anyway, if you want to use Drupal as a backend, we’ve already written about creating a RESTful server with Drupal 8. (For more additional materials, check the Useful links section at the end of this article.)

→ Client side

Getting started with Vue is really easy. But it can be even easier if you use the right tools. There is a vue-awesome project that includes a list of all kinds of tools, components libraries, and plugins for any occasion.

Getting started with Vue

Installing Vue CLI

I highly recommend using Vue CLI for jump-starting a new project. Vue.js CLI is a globally installed npm package that provides the ability to quickly scaffold a new project.

You can start a project with some of the official Vue project templates or one of many open-source single-page application templates. You also can create your own template and use it anywhere.

Vue template example

First of all, we need to install Vue CLI as a global package:

$ npm install -g vue-cli

Then initialize the project with the chosen template. I use webpack-simple, which I believe will be enough for this type of project.

$ vue init webpack-simple vue-spa

Then go to the vue-spa folder and run npm install in a terminal. We can run our application in the development mode after installing all the packages.

$ npm run dev

This will automatically launch our project on the webpack dev server. We can see our simplest Vue app in the browser. Of course, it does not look what we want it to, but this is just a baseline. Before we continue, I suggest getting acquainted with the structure of our template.

Webpack-simple template

Inside the webpack-simple template, we have the following structure:

vuejs spa
Webpack-simple template

There is the index.html file with a simple HTML markup including only the element with the identifier “app” in the body. It will be replaced by a vue-generated DOM. That is the reason why you should not use the tag body as a root element.

In the src directory, we have the main.js file which is the entry point for webpack. Vue components are imported here. Also, here we have a root Vue instance that has two properties for now. The property ‘el’ provides the Vue instance with an existing DOM element to mount on. Another one is a render function that generates DOM from App.vue. In general, this is all we need to know about the structure of the webpack-simple template. Rather simple, right? The main part of our Vue SPA will be coded in App.vue. The .vue extension indicates that this file is a single-file vue component. It is one of Vue’s features; let's get to know it better.

Vue js single file component

vue component example
Single File Components

Each *.vue file consists of three types of blocks: <template>, <script>, and optional <style>. As a result, we can divide the project into loosely-coupled components. Inside a component, its template, logic, and styles are inherently coupled, and collocating them actually makes the component more cohesive and maintainable. So now we are ready to create our Vue.js blog.

Vue js Example

Let's get going with our Vue tutorial.

Here we have a header with the name of our blog at the top of the page. On the left side, we have a fixed sidebar where the headings of our posts will be listed. It will be something like a table of contents. And the rest of the page will be occupied by a dynamic block where the full text oа the selected post will be displayed.

vuejs example
SPA with Vue.js

Step 1 of building a SPA

First of all, remove all unnecessary lines from App.vue and create a template in accordance with our requirements.

<template>
  <div id="app">
    <header>
      <h1>Vue.js SPA</h1>
    </header>
    <main>
      <aside class="sidebar">
      </aside>
      <div class="content">
      </div>
    </main>
  </div>
</template>

Second, we will create a Vue instance with the data property that we will place in the array with our posts. For now it’s empty, but soon we will put the data received from our server inside of the array. Once observed, you can no longer add reactive properties to the root data object. It is therefore recommended to declare all root-level reactive properties upfront before creating the Vue instance.

<script>
  export default {
    data () {
      return {
        posts: []
      }
    }
  }
</script>

Also, I’ve added some styles to make our application look better. The application code lives on github.com. Just clone the repository and switch the branch by the step number to follow the application creation step by step, like this:

$ git checkout step-1

At this moment we have nothing to display in our navigation bar, so let’s get the data from our server. To do this, I chose Axios, a really easy-to-use HTTP client. You can also use any other convenient way like a Vue-resource or native fetch or even jQuery Ajax.

Step 2 of building a SPA

Install Axios.

$ npm install --save-dev axios

Then import it into a component App and create a method getAllPosts(). We will make a request to the Drupal server and set it to the property post. Call the method in the hook created() that will be called after the Vue instance is created and data observation has been set up.

import axios from 'axios'
export default {
  data () {
    return {
      posts: null,
      endpoint: 'https://jsonplaceholder.typicode.com/posts/',
    }
  },

  created() {
    this.getAllPosts();
  },

  methods: {
    getAllPosts() {
      axios.get(this.endpoint)
        .then(response => {
          this.posts = response.data;
        })
        .catch(error => {
          console.log('-----error-------');
          console.log(error);
        })
    }
  }
}

And now display all the headings of articles in the sidebar.

<aside class="sidebar">
  <div v-for="post in posts">
    {{ post.title }}
  </div>
</aside>

So far we have just got the headings of posts displayed but we can not see the full posts. Now I’m going to display the full post in the content section according to the chosen title in the sidebar. At the same time, I want every article to be available at its unique address.

Step 3 of building a SPA

I will use the official Vue library vue-router to implement this. As it should be clear from the name, this library allows configuring routing for our application.

Install the package:

$ npm install --save-dev vue-router

To configure router go back to the main.js file. Here we will define the settings of our router and add it to the Vue instance.

import Vue from 'vue'
import Router from 'vue-router'
import App from './App.vue'
import Post from './components/Post.vue'
import Hello from './components/Hello.vue'
Vue.use(Router)

const router = new Router({
 routes: [
   {
     path: '/',
     name:'home',
     component: Hello,
   },
   {
     path: '/post/:id',
     name:'post',
     component: Post,
     props: true,
   },
 ]
})

new Vue({
 el: '#app',
 render: h => h(App),
 router
})

In the router settings, we specify the component that should be rendered on a specified path. Since Post.vue is the only component that will be responsible for rendering each post, we don’t have to set the path for each post, all we need is to set a dynamic path.

path: '/post/:id'

This path has a dynamic segment :id which determines the specifics of our post. Herewith we have the access to this segment in the component Post via this.$route.params.id. However, using $route in our component creates a tight coupling with the route limits the flexibility of the component as it can only be used on certain URLs. Instead of this, we can use the option props and set it to true. After that, the $route.params is set as the component Post props.

Now that we have a router created, we can return to our application and add a few more lines to the template.

<main>
  <aside class="sidebar">
    <router-link
        v-for="post in posts"
        active-class="is-active"
        class="link"
        :to="{ name: 'post', params: { id: post.id } }">
      {{post.id}}. {{post.title}}
    </router-link>
  </aside>
  <div class="content">
    <router-view></router-view>
  </div>
</main>

Here we have two components of vue-router: <route>r-link and <router-view>. The first one is the component for enabling user navigation in a router-enabled app. The second component is a functional component that renders a matched component for the given path.

There is only one step left: we need to display the contents of the post.

Step 4 of building a SPA

Let’s go to the Post.vue file and create a simple template:

<template lang="html">
  <div class="post" v-if="post">
    <h1 class="post__title">{{ post.title }}</h1>
    <p class="post__body">{{ post.body }}</p>
    <p class="post__id">{{ post.id }}</p>
  </div>
</template>

Then we need to set Vue instance settings for this component. Here everything will be very similar to the settings for displaying all posts. Declare the option props with variable id, which will get the number of our post. Next, define the data object the same as in App.vue:

import axios from 'axios';
export default {
  props: ['id'],
  data() {
    return {
      post: null,
      endpoint: 'https://jsonplaceholder.typicode.com/posts/',
    }
  }
}

Then create the method getPost() which will get only one post by an identifier and call it in the created()hook.

methods: {
  getPost(id) {
    axios(this.endpoint + id)
      .then(response => {
        this.post = response.data
      })
      .catch( error => {
        console.log(error)
      })
  }
},
  
created() {
  this.getPost(this.id);
},

Almost done. If we run the application now, we’ll see that although the URL changes, we only see the post that was rendered first. The point is that for the different posts rendering we have the same component and Vue doesn’t need to recreate it because of the extra waste of resources. This also means that the lifecycle hooks of the component will not be called.

To fix this, we just need to set a watcher for the $route object.

watch: {
  '$route'() {
    this.getPost(this.id);
  }
}

Now it works exactly as it should. To get a production version of this Vue application, just run npm run build in your terminal.

Conclusion

This was a four steps Vue.js tutorial to building single-page applications. We learned how to start a Vue project with Vue CLI. We figured out the concept of Vue single-file components which make your project more flexible and scalable. We learned how to fetch data from external API using Axios. And we saw how to configure routing with the vue-router. It is basic knowledge, of course, but I hope it will help you to start using Vue.js with more power. 

Useful links

  1. Link to the GitHub project 
  2. The vue-awesome project 
  3. RESTful Web Services in Drupal 8: quick start guide
  4. How to create a headless Drupal site
  5. What's the difference between single-page application and multi-page application?
  6. The easiest way to improve your Vue.js application. Part 1
  7. MongoDB, Express.js, React.js, Node.js - What makes this technology stack one of the best?
Still have questions? Drop us a line on

You might also like

React.js components

What are React.js components?

React follows the principles of component-oriented programming that added the notion of component — a reusable part of code — to the developers’ vocabulary. The high speed of development on React the React is favored for is based exactly on the components.
inner-big

A website without CMS: what the hell are you?

The development of online stores and interactive online services based on CMS, frameworks, and other tools can be left to the care of web developers. If you need a simple landing page or an online business card, front-end developers will come to help.