Simple steps for successful SEO with Nuxt 2

Nuxt
SEO
August 5, 2021

In this post I explain how to successfully implement SEO aspects like OGP, JSON-LD and good HTML structure technically in Nuxt.js.

Thumbnail: Simple steps for successful SEO with Nuxt 2

This article is about SEO in Nuxt 2. If you are using Nuxt 3 you can read the article Ultimate SEO Guide for Nuxt 3.

Basics

Title & Description

The minimum requirement for search engine optimization is probably the title and description of each page. These two pieces of information are displayed, for example, when you do a Google search for your website.

Meta title & description in Google search results
Meta title & description in Google search results

In Nuxt, this can be easily set in the head method of one of your pages.

Vue
pages/index.vue
<script>
  export default {
    head() {
      return {
        title: 'Creative Websites | Digital Solutions | Online Marketing',
        meta: [
          {
            hid: 'description',
            name: 'description',
            content: 'I offer your business digital solutions ...'
          }
        ]
      };
    }
  }
</script>

Note that the title and description should contain important keywords and only a certain number of characters for best results. However, this post will only look at the technical implementation.

Title variable

If you want to present your initials as a constant in the title of each page, you can specify this in titleTemplate of the head property inside your Nuxt configuration.

JavaScript
nuxt.config.js
export default {
  head: {
    titleTemplate: '%s - thenextbit'
  }
};

The %s is a placeholder for the title that you specify in the head method of a page, and will be automatically replaced with the title of the page.

Language

You should always specify which language your page is in. If your site has only one language, you can set it globally in Nuxt configuration. You just need to specify the lang property inside the HTML attributes.

JavaScript
nuxt.config.js
export default {
  head: {
    htmlAttrs: {
      lang: 'en'
    }
  }
};

If your website is multilingual, you should set the language dynamically. For example, if you are using the i18n Nuxt module, you could set the following code in your default.vue layout.

Vue
layouts/default.vue
<script>
  export default {
    head() {
      return {
        htmlAttrs: {
          lang: this.$i18n.locale
        }
      };
    }
  };
</script>

Canonical

The canonical tag describes where (URL) the original content of your page is located and should be set for each page. Search engines will consider the specified URL with increased relevance.

Vue
pages/index.vue
<script>
  export default {
    head() {
      return {
        link: [
          {
            hid: 'canonical',
            rel: 'canonical',
            href: 'https://thenextbit.de/'
          }
        ]
      };
    }
  };
</script>

OGP - Open Graph protocol

OGP is an important part through which you store specific metadata for each page that describes a page. This information allows users who share your page on social media, for example, to get a detailed preview.

OGP preview on Twitter
OGP preview on Twitter

OGP can be implemented with <meta> tags. For this, we again use the head method of a page.

You should specify at least the title, type, image, and the URL (for OGP) of the page. You can of course specify other properties like a description or the language. For more in depth information, read further on the Open Graph protocol website.

Vue
pages/index.vue
<script>
  export default {
    head() {
      return {
        meta: [
          {
            hid: 'og:title',
            property: 'og:title',
            content: 'Creative Websites | Digital Solutions | Online Marketing'
          },
          {
            hid: 'og:description',
            property: 'og:description',
            content: 'I offer your business digital solutions ...'
          },
          {
            hid: 'og:type',
            property: 'og:type',
            content: 'website',
          },
          {
            hid: 'og:image',
            property: 'og:image',
            content: 'https://thenextbit.de/seo/og_image.jpg'
          },
          {
            hid: 'og:url',
            property: 'og:url',
            content: 'https://thenextbit.de/'
          },
          {
            property: 'og:locale',
            content: 'de_DE'
          }
        ]
      };
    }
  };
</script>

JSON-LD

JSON-LD is a standardized format for writing structured data. If done correctly, search engines like Google can understand what content your website has.

Setup

You can use the nuxt-jsonld module to manage JSON-LD in Vue components.

As this guide is for Nuxt 2, we will use nuxt-jsonld@v1 in the following example. If you are using Nuxt 3, then just install nuxt-jsonld.

bash
NPM
npm install nuxt-jsonld@v1

Create a matching plugin named jsonld.js in your plugins directory. Don't forget to include the plugin in the Nuxt configuration as well.

JavaScript
plugins/jsonld.js
import Vue from 'vue';
import NuxtJsonld from 'nuxt-jsonld';

Vue.use(NuxtJsonld);
JavaScript
nuxt.config.js
export default {
  plugins: [
    '@/plugins/jsonld.js'
  ]
};

Now the jsonld method is available to you in all of your pages.

Implementation & results

To better understand the concept, let me show you what the structured data looks like for one of my blog posts. We have a jsonld method that has various entries.

Vue
pages/blog/_id.vue
<script>
  export default {
    jsonld() {
      return {
        '@context': 'https://schema.org',
        '@type': 'NewsArticle',
        mainEntityOfPage: {
          '@type': 'WebPage',
          '@id': 'https://thenextbit.de/blog/...'
        },
        headline: '...',
        description: '...',
        image: '...',
        author: {
          '@type': 'Person',
          name: '...'
        },
        datePublished: '...',
        dateModified: '...',
        publisher: {
          '@type': 'Organization',
          name: 'thenextbit',
          logo: {
            '@type': 'ImageObject',
            url: 'https://thenextbit.de/logo/thenextbit.png'
          }
        }
      };
    }
  };
</script>

The search engine can then interpret the specified information.

JSON-LD interpreted data for an article
JSON-LD interpreted data for an article

You can test your structured data at any time with this tool from Google.

On Schma.org, you can see all the different types and parameters that are available. You will get, if you structure your data, advanced and visually appealing search engine benefits like in the following image.

JSON-LD example (Image by Google)
JSON-LD example (Image by Google)

HTML

Headline hierarchy

Search engines try to understand your website. It is advisable to establish a well-structured heading structure. For example, each page should only have one <h1> element.

HTML hierarchy for headings
HTML hierarchy for headings

But you don't have to check the structure manually in your HTML. There are tools for this. If you use Google Chrome, you could use META SEO inspector or something similar.

Links

Links should always contain a text and the relation attribute. Search engines try to understand these links. Include this information with all links.

The list of possible rel values can be seen in this MDN article.

Vue
<nuxt-link to="/blog" rel="next">
  All articles
</nuxt-link>

<a href="mailto:info@thenextbit.de" rel="help">
  info@thenextbit.de
</a>

Images

For images, it is similar to links, only with different attributes.

  • Each image should contain an alternative text alt that will be displayed in case the image fails to load.

  • The title is also helpful for accessibility and understanding the context by search engines.

  • The loading attribute specifies when the image should load. Suppose an image is far outside the visibility area - then the image does not necessarily have to be loaded. In this case, the value can be lazy. The image will load only when you approach it. If you want the image to load immediately, specify the value eager.

Vue
<img
  src="/services/websites.png"
  alt="Brillant and intuitive Websites"
  title="Brillant and intuitive Websites"
  loading="lazy"
/>

Sitemap

Every website should have a sitemap. The sitemap allows your website to be indexed by search engines. It contains all the subpages of the website that you want to show as well. Mostly, the sitemap is called sitemap.xml and is addressable in the main directory of a page.

Setup

You can use the Nuxt Sitemap Module.

bash
NPM
npm i -D @nuxtjs/sitemap

Add it to the modules array inside your Nuxt configuration.

JavaScript
nuxt.config.js
export default {
  modules: [
    '@nuxtjs/sitemap'
  ]
};

Now, a sitemap should be already created automatically for all your static pages. You can view it at /sitemap.xml.

Configuration

You can configure your sitemap, for example, by specifying pages that should not be indexed with the exclude field.

Furthermore, you could also specify dynamic routes (e.g., your blog) in the routes field.

JavaScript
nuxt.config.js
export default {
  sitemap: {
    hostname: 'https://thenextbit.de/',
    gzip: true,
    exclude: ['/non-indexed-page'],

    // The implementation, how the routes are obtained is up to you
    routes: async () => {
      const routes = [];
      const posts = await getPosts();
      posts.forEach((post) => {
        routes.push('/blog/' + post.uid);
      });
      return routes;
    }
  }
};

The routes parameter follows the same way as inside the generate property.

Specifying routes for @nuxtjs/sitemap is optional. Your dynamic routes can be set inside the generate.routes instead of sitemap.routes property in the Nuxt configuration (the same way as above). The routes will be automatically recognized by @nuxtjs/sitemap.

Favicon

A successful website should also show a logo on all devices. Whether in the tabs in the web browser on the desktop or as a bookmark on the homescreen of a smartphone - you should consider all forms.

For this purpose, there is a really handy tool called realfavicongenerator. You can upload your logo, and it will help you convert it to all formats. After that, you can download the favicons and paste them into your ~/static/ directory.

The only thing left for you to do is to include the favicons and metadata in the Nuxt configuration. This is also explained to you step by step by realfavicongenerator.

This is how it could look like:

JavaScript
nuxt.config.js
export default {
  head: {
    meta: [
      {
        name: 'msapplication-TileColor',
        content: '#ffffff'
      },
      {
        name: 'theme-color',
        content: '#ffffff'
      }
    ],

    link: [
      {
        rel: 'icon',
        type: 'image/png',
        sizes: '16x16',
        href: '/favicon-16x16.png'
      },
      {
        rel: 'icon',
        type: 'image/png',
        sizes: '32x32',
        href: '/favicon-32x32.png'
      },
      {
        rel: 'apple-touch-icon',
        sizes: '180x180',
        href: '/apple-touch-icon.png'
      },
      {
        rel: 'mask-icon',
        href: '/safari-pinned-tab.svg',
        color: '#581bdf'
      },
      {
        rel: 'manifest',
        href: '/site.webmanifest'
      }
    ]
  }
};

Conclusion

I hope you got a good overview of the different ways to implement SEO with Nuxt.js.

Update: in the following post Advanced SEO-Workflow with Nuxt.js, you can learn more advanced concepts for SEO with Nuxt.