How to Build a SEO-friendly Website Using Nuxt.js

I've built an SPA application before only to realize in the end that it wasn’t being completely crawled by search engines. This is a common mistake. This article is aimed at those that love Vue.js, want to use it and need to build a fully optimized SEO application with it.

Javier Munoz Tous
Frontend Developer

What is Nuxt.js?

The objective of Nuxt.js is to make the creation of universal Vue.js applications easier - universal meaning both the client and server sides.

This is achieved by abstracting the client and server, so that our programming is independent of where it is running.

One example of what Nuxt.js can do is: imagine we have a page displaying Cryptocurrencies with their name, symbol, logo and price. In a client-side application, we would fetch the data from the endpoint, and the corresponding Vue.js component would take care of modifying the DOM of the page to show the data.

By running the same code server-side with Nuxt.js, we will receive the same page rendered, connected to the same endpoints with the populated data, and the generated DOM in the form of HTML in the browser. There is more though. Our application, as we have received it, is still reactive, so we can update prices periodically by calling the same endpoint, but this time on the client side. We don't lose that reactivity that we like from Vue.js.

This is a huge advantage because with the same code, we can have a website that is SEO-friendly just by creating it using Nuxt.js.

Making your VueJs website SEO friendly

This tutorial relies heavily on what we have learned as a result of the solutions we applied to our project https://cryptoticker.cc/, a free cryptocurrency exchanges comparison tool built using Nuxt.js in a short amount of time. Examples we'll be using will be based on scenarios related to that project.

Installation

If you already have a Vue.js project, and you want to convert it to Nuxt.js; you will have to do that from scratch. While that may sound challenging, it is easier than it seems. I will provide some guidance, specifically on that, at the end of this article, but if you follow the next steps, you will more than likely be able to do that on your own.

First, let us create our Nuxt.js project. For that, you need yarn to be installed globally on your machine:

$ yarn create nuxt-app <my-project>

Then, the wizard will ask you some questions:

1. Project name - This will map to package.json name
2. Project description - This will map to package.json description
3. Use a custom server framework - If you want to integrate a server framework on the same app, choose whichever you like. Personally, I don’t use it for my projects.
4. Choose features to install - Choose wisely depending on your needs but usually I require all of them. PWA support is a necessity in my opinion.
5. Use a custom UI framework - You can choose if you want a UI framework to be installed and configured in your app. I usually use Vuetify.
6. Use a custom test framework - Use the one you like or none.
7. Choose rendering mode - Here you must choose Universal. You can switch modes later if you want to, but this one is necessary to build an SSR website.
8. Author name
9. Choose package manager (I recommend yarn)

After it's done installing, navigate to your project $ cd <my-project>.

Creating our routes

This step is fairly simple. The project structure for Nuxt.js is very similar to a Vue.js project. One of the most significant differences is that the routes are generated dynamically by Nuxt.js based on your page's folder structure.

So, imagine we want to create the following: A homepage route that we want to be the main landing for our users to quickly discover exchanges. Then, we have a compare route, where we want the user to be able to compare cryptocurrencies from different exchanges. Finally, we want a dynamic route that will take an exchange key and display the details. All this can be configured by the way we name the files and folders inside the page's directory. This will be our page's folder structure:

pages
├── exchanges        
│        ├── _key
│        │       ├──  index.vue // route will be "/exchanges/:key"
├── index.vue          // route will be "/"
├── compare.vue    // route will be "/compare"

That’s it! We don’t need to configure anything else. Our app will now be ready to serve these pages. If you want to learn more about routing and the built-in properties for the page components, look at the Nuxt.js routing documentation https://nuxtjs.org/guide/routing.

Prepopulate Page Data

To be able to serve the browser a fully-rendered page that is SEO-friendly and contains the necessary keywords, we need to fetch the data on the server side.

Nuxt.js provides us with a useful property on the pages components called asyncData, which is essentially like the normal Vue.js data property except it is resolved on the server.

If we installed axios, we can do the following to get exchanges data on the server side:

async asyncData({
    params,
    $axios
}) {
    const {
        data
    } = await $axios.get(`/api/exchanges/${params.key}`)
    return {
        exchangesData: data
    }
},

You can then use “exchangesData” anywhere in your template including computer properties, methods, and more. It will be resolved before the page is served to the client.

The property “exchangesData” will still be reactive and if you assign a new value - for example if you request new data, it will react to the changes.

Global, Page specific and Dynamic Page Meta tags

Nuxt.js provides us with an almost effortless and maintainable way of adding meta tags to our website. We can configure the global meta tags on the head property of nuxt.config.js.

Meta tags will be added to every page of your website. It is very important to add the hid property to the meta object because it will prevent duplicates that can severely impact your SEO.

For example, this could be a head property in our nuxt.config.js.

// ... import stuff here

export default {
  // ... other configuration properties here
  /*
   ** Headers of the page
   */
  head: {
    title: 'Crypto Ticker',
    meta: [
      { charset: 'utf-8' },
      {
        hid: 'viewport',
        name: 'viewport',
        content: 'width=device-width, initial-scale=1'
      },
      {
        hid: 'twitter:card',
        name: 'twitter:card',
        content: 'Cryptocurrency Compare Tool | 2amigOS'
      },
      { hid: 'twitter:site', name: 'twitter:site', content: '@2amtech' },
      { hid: 'twitter:creator', name: 'twitter:creator', content: '@2amtech' },
      {
        hid: 'twitter:title',
        name: 'twitter:title',
        content: 'Crypto Ticker - A cryptocurrency comparison tool'
      },
      {
        hid: 'twitter:description',
        name: 'twitter:description',
        content: pkg.description
      },
      {
        hid: 'twitter:image',
        name: 'twitter:image',
        content: '/crypto-ticker-snapshot'
      },
      { hid: 'description', name: 'description', content: pkg.description },
      {
        hid: 'og:image',
        property: 'og:image',
        content: '/crypto-ticker-snapshot.png'
      },
      {
        hid: 'og:site_name',
        name: 'og:site_name',
        content: '2amigOS Crypto Ticker'
      },
      { hid: 'og:title', name: 'og:title', content: 'Crypto Ticker' },
      {
        hid: 'og:description',
        name: 'og:description',
        content: pkg.description
      }
    ],
    link: [
      { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
      {
        rel: 'stylesheet',
        href:
          'https://fonts.googleapis.com/css?family=Roboto:300,400,500,700|Material+Icons'
      }
    ]
  },

  // ... more configuration options here ...
}


These global meta tags are useful but what if we want to add meta tags that are specific to our pages? Also, what if this page is dynamic, such as a details page? For that, we can use the head property of our page components. This head has the context of the component and therefore you have access to your data properties.

// ... other code 
head() {
    return {
        title: this.title,
        meta: [{
            hid: 'description',
            name: 'description',
            content: this.exchange.details
        }, {
            hid: 'twitter:card',
            name: 'twitter:card',
            content: `Crypto Ticker - ${this.exchange.name}`
        }, {
            hid: 'twitter:title',
            name: 'twitter:title',
            content: `Crypto Ticker - ${this.exchange.name}`
        }, {
            hid: 'twitter:description',
            name: 'twitter:description',
            content: this.exchange.details
        }, {
            hid: 'og:title',
            name: 'og:title',
            content: `Crypto Ticker - ${this.exchange.name}`
        }, {
            hid: 'og:description',
            name: 'og:description',
            content: this.exchange.details
        }]
    };
}
// ... other code

That’s it! Every detail route will have a meta tag with dynamic content. That’s awesome!

Add Google Analytics

Adding Google Analytics in a Nuxt.js app is very easy. Start by running the following command:

$ yarn add @nuxtjs/google-analytics

Then, go to your application config in nuxt.config.js and add the following to the modules array:


// ... other configuration options 
modules: [
    '// ... other modules 
    '@nuxtjs/google-analytics'  // <--- This one!
  ],

// ... more configuration options here ... 

Now you can configure Google Analytics. Add this at the root level of the nuxt.config.js object:

googleAnalytics: {
    id: "{YOUR GOOGLE ANALYTICS ID}",
    dev: false
  },

Hint If instead of hardcoding the values you want to use environment variables, I highly recommend you to use the [dotenv]((https://github.com/nuxt-community/dotenv-module) package.

To make sure everything works, run your app with yarn dev and open the console. You should see the Google Analytics logs.

If you need more information about the analytics module, you can go to the nuxt github: https://github.com/nuxt-community/analytics-module

Add a Dynamic Sitemap

We can also add a dynamic sitemap into our build process by adding the Nuxt sitemap module. First, we must install it by running the following command:

$ yarn add @nuxtjs/sitemap

Then, in the same way as we did for Google Analytics, we add it to our modules array inside nuxt.config.js.


// ... other configuration options 
modules: [
    // ...  other modules
    '@nuxtjs/sitemap' // <--- This one!
  ],

// ... more configuration options here ... 

After that we can configure the sitemap build with the sitemap property in nuxt.config.js.

Check the following code - we mapped the array of routes to return an array of objects with changefreq, priority, lastmodISO and route properties. This is much easier to read than hardcoding route config object and repeating that process for each route:

sitemap: {
    path: '/cryptoticker.cc.xml',
    hostname: 'https://cryptoticker.cc',
    cacheTime: 1000 * 60 * 15,
    gzip: true,
    generate: false,
    routes: [
      '/',
      '/compare',
      '/exchanges/bibox',
      '/exchanges/binance',
      '/exchanges/bitfinex',
      '/exchanges/bittrex',
      '/exchanges/coss',
      '/exchanges/kraken',
      '/exchanges/kucoin',
      '/exchanges/poloniex',
      '/exchanges/theocean',
      '/exchanges/upbit'
    ].map(route => ({
      url: route,
      changefreq: 'monthly',
      priority: 1,
      lastmodISO: new Date().toISOString().split('T')[0]
    }))
  },

You can check if the sitemap is added correctly by building your app with yarn build, then launching the server with yarn start, and checking in your localhost:port/sitemap.xml. For more information about the sitemap module check https://github.com/nuxt-community/sitemap-module#readme.

Nuxt.js is very powerful and there is much more that you can do with it. This is only a small example of what’s possible. If you liked this tutorial and want to see a very practical example of how I used all of this together, please visit our free tool made using Nuxt.js https://cryptoticker.cc.

2amigos Consulting Group can help you build your next VueJs project. Have a need or an idea? Get in touch and let’s talk.