Optimize SEO and Social Media Sharing in a Nuxt blog

So far in this guide, the blog is full-featured since we added tags and search functionality. But still, SEO is important in a blog so we cannot overlook it.

SEO is a big thing and there are people working almost exclusively in SEO, especially those working on copywriting.

That's why I'm not covering everything about SEO here. I'm not an expert on it, but at least we can make sure the blog has the minimum stuff for having the basics sorted.

We'll make sure the pages have the correct metadata for search engines and social sharing, as well as generating a sitemap so we make easier the job for search engines.

# Meta Tags and Social Media

One of the basic things we can do is to set up the meta tags for our blog pages.

They're important for giving some extra info to the search engines, so they can show better information on the SERP (Search Engine Result Pages).

For instance, if you search "vuedose" on Google, you'll see this result thanks to the meta tags and the sitemap:

https://a.storyblok.com/f/83078/1638x842/fe5b30cf36/06-01-serp-explanation.png

I'll cover the sitemap topic later, but for now you can see at least we have a title and description meta tags.

First, set them in nuxt.config.js using the head property, just like this:

export default {
  // ...
  head: {
    title: "NarutoDose",
    meta: [
      {
        hid: "description",
        name: "description",
        content: "Get to know all about Naruto and its characters in tiny bits of info.",
      },
      // ...
    ],
  },
};

That will be translated to <meta name="description" content="Get to know..."> and inserted by Nuxt within the <head> tag. It's necessary that we set hid, otherwise the tags will be duplicated.

Since later we'll add more tags in different pages, I'll suggest to create a util function for it. Create the file utils/seo.js with the following function:

export const createSEOMeta = (data) => [
  { hid: 'description', name: 'description', content: data.description },
]

And update it on nuxt.config.js:

import { createSEOMeta } from "./utils/seo";

export default {
  // ...
  head: {
    title: "NarutoDose",
    meta: [
      ...createSEOMeta({
        description: "Get to know all about Naruto and its characters in tiny bits of info.",
      }),
      // ...
    ],
  },
};

Now we need to do it on each page as well. If a tag is not set on a page, Nuxt will fallback to the one in nuxt.config.js if it exists there, so for the home page located at pages/index.vue.

Let's go to pages/_slug.vue and set it there. In this case, it's a dynamic page, but that's no problem because the head method can access the component instance properties:

export default {
  // ...
  head() {
    const { title, description } = this.article.content

    return {
      title,
      meta: createSEOMeta({ description }),
    }
  },
}

In case you're wondering where this.article comes from, I explained in another article how to show the blog content in Nuxt using Storyblok API.

In pages/topics/_slug.vue can be more simple, since I'd just set a title and let take the rest from the default one from nuxt.config.js.

Since it's a category, I'd like it to show up in the SERP and the browser tab like CATEGORY_NAME - NarutoDose:

head() {
  return {
    title: `${this.topic.name} - NarutoDose`,
  }
},

# Social Media Sharing

Imagine you have your blog published and you want to share the link of an article on Twitter, Facebook or LinkedIn.

By default, that'd be shown as just a link. Wouldn't be cool that instead of showing just a link, it can appear in a more visual way, like this?

https://a.storyblok.com/f/83078/1192x1246/116d779bc0/06-02-social-media-tweet.png

They are called Social Cards, and they don't show up automatically. We need to add some extra tags. In particular, we need the ones for the Open Graph, a protocol coined first by Facebook but now also Twitter and LinkedIn (I can imagine Instagram as well).

We need at least the tags:

  • og:title
  • og:description
  • og:url: the absolute URL that points to the article.
  • og:image: make sure it has the right resolution
  • twitter:card: the type of card you want to show on Twitter. We'll use summary_large, give it a look at Twitter docs.

You could use some extra ones if you want to polish it even more, but like these we're ok.

Let's add these tags to the createSEOTags util function we defined before:

export const createSEOMeta = (data) => [
  { hid: 'og:title', property: 'og:title', content: data.title },
  { hid: 'description', name: 'description', content: data.description },
  {
    hid: 'og:description',
    property: 'og:description',
    content: data.description,
  },
  { hid: 'og:image', property: 'og:image', content: data.image },
  {
    hid: 'og:url',
    property: 'og:url',
    content: process.env.HOST_NAME + '/' + data.url,
  },
  {
    hid: 'twitter:card',
    name: 'twitter:card',
    content: data.cardType || 'summary_large_image',
  },
]

Since the url must be absolute, probably it's a good time for you to add a HOST_NAME variable on you .env file.

We have all the data we need to set these tags, except the image. So first of all let's go to our Storyblok space and add an asset field to the Article schema.

https://a.storyblok.com/f/83078/1280x1164/a910dc55b2/06-03-storyblok-asset.png

If you don't know how to do it, take a look at how to set up your blog structure on Storyblok, I've explained how to add fields to a schema there 😉.

Then, go through each article and update them by adding an image to each one.

Once you do that, go to pages/_slug.vue and update the head function:

head() {
  const url = this.article.slug
  const { title, description, image } = this.article.content

  return {
    title,
    meta: createSEOMeta({ title, description, image: image.filename, url }),
  }
},

Notice the image url is located on image.filename.

Do the same in nuxt.config.js:

import { createSEOMeta } from "./utils/seo";

export default async () => {
  const routes = await fetchSitemapRoutes();

  return {
    head: {
      title: "NarutoDose",
      meta: [
        ...createSEOMeta({
          title: "NarutoDose",
          description: "Get to know all about Naruto and its characters in tiny bits of info.",
          image: "[Insert_NarutoDose_Image_URL]",
          url: process.env.HOST_NAME,
        }),
      ],
    },
  };
};

That'd be basically it. If you need to set specific tags on pages/topics/_slug.vue you're free to do it.

# Sitemap

A sitemap tells search engines how's the page structured. That not only makes your pages indexable, but it makes the job easier to search engines and will be able to show the different sections of your site as you saw on the first image above.

To generate a sitemap, let's use @nuxtjs/sitemap: a Nuxt module that makes the job easy. Just install it and add it to the modules section on nuxt.config.js. Then, in the same file, you need to add this minimal configuration:

sitemap: {
  hostname: process.env.HOST_NAME,
  routes: [] // all the dynamic routes
},

The module will pick up by default the static routes, but it's not able to pick up the dynamic routes. So we have to fetch them and add it to it.

I'll suggest to do that in a utility function, so create a utils/sitemap.js file with the following content:

import StoryblokClient from 'storyblok-js-client'
import kebabCase from 'lodash/kebabCase'

export const fetchSitemapRoutes = async () => {
  const routes = []
  const client = new StoryblokClient({ accessToken: process.env.STORYBLOK_KEY })

  const { data: articlesData } = await client.get('cdn/links', {
    starts_with: 'articles/',
  })
  const { data: tagsData } = await client.get('cdn/tags')

  Object.values(articlesData.links).forEach((article) =>
    routes.push(`/${article.slug}`)
  )
  tagsData.tags.forEach((tag) => routes.push(`/topics/${kebabCase(tag.name)}`))

  return routes
}

We use storyblok-js-client to fetch the data. You don't need to install it since it comes by default with storyblok-nuxt module.

Then we fetch all the articles and tags and form an array of strings with all the routes. Notice that for the articles we use the Links Endpoint, which is much faster and convenient for cases when you don't need the stories content. You don't even need to paginate them.

Finally, we need to use that function to fill up the routes. By default, we're exporting an object from nuxt.config.js, but for cases like this where we need to perform an async operation like fetching the routes, we can export a function instead:

import { fetchSitemapRoutes } from "./utils/sitemap";

export default async () => {
  const routes = await fetchSitemapRoutes();

  return {
    // ...
    sitemap: {
      hostname: process.env.HOST_NAME,
      routes,
    },
    // ...
  };
};

That's it. To test if it works, make sure you have a generate script in your package.json and run npm run generate.

A dist folder must've been generated, containing the right sitemap.xml file on it:

https://a.storyblok.com/f/83078/1766x1114/ef6d98be96/06-04-sitemap.png

# Wrapping Up

It's important not to have just a blog, but to make sure your blog it's minimally prepared for SEO and being positioned.

It's true that you can do much more to improve SEO, mostly dependent on the quality of the content you write. But at least with the right meta tags and a sitemap you cover the basics of it.

Now that the blog it's ready, what's next?

The last step: deploy it 😉

Coming next week!

Don't miss out anything about Vue, your favourite framework.

Subscribe to receive all the articles we publish in a concise format, perfect for busy devs.

Related Articles

Nuxt 3 + Storyblok for a Sushi Recipes Website

Have you used Nuxt 3 with Storyblok? Learn how to build a real-world recipes website from scratch (includes a video so you can see all the process)

Alvaro Saburido Rodriguez

Alvaro Saburido Rodriguez

Dec 15, 2021

Generate and deploy the blog as a full static Nuxt site

Your site is ready for the world. Wait, where and how do I publish it? You only need 5 minutes to learn how to deploy a Nuxt.js site in Netlify.

Alex Jover Morales

Alex Jover Morales

Aug 25, 2020

The new Provide and Inject in Vue 3

Getting stuck into the prop drilling? Learn how provide/inject can make your components more flexible and independent in this short tutorial.

Anthony Konstantinidis

Anthony Konstantinidis

Jul 18, 2022

Sponsors

VueDose is proudly supported by its sponsors. If you enjoy it, consider supporting it to ensure the project maintainability.

Silver
Learning Partner