DEV Community

Cover image for How Nuxt.js solves the SEO problems in Vue.js
Brian Neville-O'Neill
Brian Neville-O'Neill

Posted on • Originally published at blog.logrocket.com on

How Nuxt.js solves the SEO problems in Vue.js

Written by Preetish HS✏️

What exactly is the problem with vanilla Vue.js and SEO?

Vue.js, like many other frameworks such as React, Angular, etc., is a client-side framework, meaning the webpage is rendered by running JavaScript on the client side. These apps are commonly called single-page applications, or SPAs.

When an SPA is loaded on the browser, the server only sends the basic HTML without any rendered content. It makes another request to fetch the JavaScript bundle. JavaScript then runs in the browser to render the content. When we view the page source, we see something like the below:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>My Blog</title>
  </head>
  <body>
    <div id="app"></div>
    <script src="/js/app.js"></script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

See the problem there? When search engine bots crawl your page, they only get this bare HTML without any content. There is no data for the bots to use to index your page.

Well, SPAs have been around for quite a while now, and Google does say their crawlers can index SPAs now. There is quite a lot of uncertainty there. How long do the crawlers wait on your page? What if your bundle size is too big? what if, due to some error, the page couldn’t render properly? Does it retry?

Let’s assume it was successfully able to render the client-side code and index it properly. Does it mean your page is now optimized for search? There are many criteria that contribute page rank, and page download speed is among the most important. An SPA is generally slower on first content paint compared to old-school static HTML/CSS pages as there is the overhead of making an Ajax call to fetch the bundle and render it.

We have come a long way from those static HTML/CSS pages, so obviously, we can’t go back there again. Those methods had their own problems — each request has to go to the server to fetch specific and common data, download new stylesheets for different pages each time the user navigates, etc.

LogRocket Free Trial Banner

Is there a solution that utilizes the best features of both methods, that is having great SEO and also be super fast like a SPA? Well, hello SSR!

Server-side scripting is a technique used in web development that involves employing scripts on a web server that produce a fully rendered page. This page is then returned to the client application. SSR produces faster page loads since all the content is already rendered in the server. Let’s build one such application with Nuxt.js

Building a simple web application with Nuxt.js

Run the following command to create a Nuxt app:

npx create-nuxt-app nuxt-blog-seo
Enter fullscreen mode Exit fullscreen mode

You get the following options. My setup looks like the image below:

Alt Text

If you are new to the Nuxt framework, then there a few things Nuxt does differently compared to Vue:

  1. Folder structure: Nuxt follows a strict folder structure which should not be modified
  2. Routing: Nuxt uses the pages directory to get the routing structure (it does automatic code splitting 🙌. You can add an external routing file, but it’s not required.
  3. Router links: Instead of <router-link>, Nuxt uses a special tag <nuxt-link>.

Now go to the pages folder and modify the index.vue file with the following code:

<template>
  <div class="container">
    <h1>welcome to my page</h1>
    <div>
      Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cum ex modi
      sapiente amet temporibus exercitationem qui nihil eius, nam sequi sint
      iste nostrum corrupti, similique in vel impedit inventore id!
    </div>
  </div>
</template>

<script>
export default {}
</script>
Enter fullscreen mode Exit fullscreen mode

Run the application using the npm run dev command. Open the webpage and go to view page source, and voilà! We see our content in the page source.

[IMAGE]

Let’s add one more page and a link to the index.vue file:

<!-- Article.vue -->
<template>
 <div class="container">
  <h1>A dummy article</h1>
  <div>
   Lorem ipsum dolor sit amet, consectetur adipisicing elit. Cum ex modi
   sapiente amet temporibus exercitationem qui nihil eius, nam sequi sint
   iste nostrum corrupti, similique in vel impedit inventore id!
  </div>
 </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Now let’s add a link to this page in our main page:

<nuxt-link to=”/Article> My Article </nuxt-link>
Enter fullscreen mode Exit fullscreen mode

Save it and run the app again and you’ll be able to navigate to this page. Did you notice that the page opens instantly, just like how an SPA would work? After the first page load, Nuxt behaves like an SPA. View the source again and we can see the full source of the Article.vue page, too! This is because Nuxt creates a static version of the website (for static assets).

In your Article.vue file, instead of using dummy hardcoded data, let’s fetch it from the web this time. For this purpose, I’ll make use of json placeholder api and axios. We added the Axios module when we created the application; it can be accessed in our Vue components like a plugin:

 this.$axios
   .get('http://jsonplaceholder.typicode.com/posts/1')
      .then((res) => {
        return { fetchedData: res.data }
    })
Enter fullscreen mode Exit fullscreen mode

Where do we add this code? We could add this in the created() hook, but created() runs only on the client side, and that’s not what we want.

Nuxt asyncData

asyncData tells Nuxt to render that particular part of the code on the server. When it runs on the server, our Vue component isn’t initialized yet; thus, we cannot use this or any methods here. However, we receive Nuxt’s context object, which has all that data.

<template>
  <div class="container">
    <h1>{{ fetchedData.title }} test</h1>
    <div>
      {{ fetchedData.body }}
    </div>
  </div>
</template>
<script>
export default {
  asyncData(context) {
    return context.$axios
      .get('http://jsonplaceholder.typicode.com/posts/1')
      .then((res) => {
        return { fetchedData: res.data }
      })
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

Open the page again and check the page source. We see the server has already rendered the data. Great! 🎉

How does Nuxt do this?

Nuxt internally runs a real-time Node server. Thus, it’s able to pre-render the pages before it’s even sent to the client. To host this application, we need a server capable of running Node.js.

Does that mean we can no longer host it on static hosting providers like Netlify? Well, yes — that’s the sacrifice we need to make. But we’ll come back to this problem later.

Let’s add a Vuex store to our little project

There is no need to install Vuex since Nuxt automatically does it when it sees content in the store folder.

I want to show the username in both the homepage and the article page. We need to fetch this from the server. Instead of fetching it in both places, let’s fetch it once and store it in Vuex.

Create a user module in Vuex by creating a new file, user.js, inside the store folder:

export const state = () => ({
  userName: 'default'
})
export const mutations = {
  updateUserName(state, value) {
    state.userName = value
  }
}
export const actions = {
  getUserName(context) {
    return this.$axios
      .get('https://jsonplaceholder.typicode.com/users/1')
      .then((res) => {
        context.commit('updateUserName', res.data.name)
      })
  }
}
Enter fullscreen mode Exit fullscreen mode

Here, I am fetching the userName from the server. Let’s display this on both pages:

<div>Name: {{ $store.state.user.userName }}</div>
Enter fullscreen mode Exit fullscreen mode

We could call the action getUserName in the asyncData, method which runs on server, but Nuxt provides a special action method called nuxtServerInit.

nuxtServerInit method

This method is called automatically by Nuxt on the server. We can use this to populate the store on the server side. This method can only be used in the primary module, so let’s create an index.js file in the store folder:

export const actions = {
  async nuxtServerInit(vuexContext) {
    await vuexContext.dispatch('user/getUserName', { root: true })
  }
}
Enter fullscreen mode Exit fullscreen mode

Now the action getUserName will be automatically called, and userName will be populated on the server side. Similarly, we can call any number of actions from different modules inside the nuxtServerInit.

How about meta tags?

Meta tags are one of the most important factors that impact SEO. Nuxt uses vue-meta internally to generate the contents of the <head> tag, such as page title, meta tags, etc.

So what’s special here? We can use vue-meta in vanilla Vue.js, too. In the case of Vue.js, the meta tags are populated at the same time the JavaScript renders the page, so the bots may or may not pick up the meta tags.

In such cases where the meta tags are populated based on the subsequent Ajax call, we can also see the page title dynamically changing after the response is received. The page source will not have meta tags. This is pretty bad for SEO.

On the other hand, Nuxt pre-renders the meta tags, too! Even if there is a subsequent Ajax call, we can call that in asyncData or in nuxtServerInit, which are executed in the server. So in all cases, the bots get the updated meta tags when they crawl our page! Let’s see this in action.

Let’s add page title and meta tags to our article page:

export default {
  asyncData(context) {
    return context.$axios
      .get('http://jsonplaceholder.typicode.com/posts/1')
      .then((res) => {
        return { fetchedData: res.data }
      })
  },
  head() {
    return {
      title: this.fetchedData.title,
      meta: [
        {
          hid: 'description',
          name: 'description',
          content: this.fetchedData.body
        }
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

After reloading the page, view the page source and we can see both of them reflected there.

Alt Text

Static mode

Nuxt can generate a static version of the website that is SEO-friendly and does not require us to run a real-time Node server backend to get all the benefits. We can just host it on static servers like any other Vue application and still have all the benefits of SEO.

To build in static mode, use the following command — it generates the code for all possible routes in the dist directory:

npm run generate
Enter fullscreen mode Exit fullscreen mode

There we have it! 😃

Nuxt is designed with SEO in mind. With Nuxt, we can take control of many factors that impact SEO and page ranking. Nuxt fills the gaps and shortcomings of SPAs and makes the process of creating an SEO-friendly application easy and enjoyable.


Experience your Vue apps exactly how a user does

Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, try LogRocket.

Alt Text

LogRocket is like a DVR for web apps, recording literally everything that happens in your Vue apps including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.

The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error, and what state the application was in when an issue occurred.

Modernize how you debug your Vue apps - Start monitoring for free.


The post How Nuxt.js solves the SEO problems in Vue.js appeared first on LogRocket Blog.

Top comments (0)