How to Make Your Gridsome Site SEO-Ready: Meta-Tags, Sitemap, Robots.txt

August 14, 2020 · 7 min read

Colorful letters S, E, and O on white background Photo by Merakist on Unsplash

This is part twelve of a series on building a file-based blog from scratch with Gridsome. Find the full series here.

SEO is crucial. And with site speed being a big factor in SEO, we are set up for a good start with Gridsome.

Great technical SEO is the basis for all other SEO techniques. Here are the basics:

  • Site render speed
  • Site download speed
  • Use HTTPS
  • Mobile optimized
  • Good Lighthouse Scores
  • robots.txt
  • sitemap.xml
  • necessary meta tags

Gridsome is already great at site render speed and your overall Lighthouse score. But it's always worth to check yourself. Make sure your site is accessible, looks great on mobile, and that you optimize your images.

To get the site download speed and HTTPS right, I strongly recommend to use a service like Netlify, Vercel, or GitHub Pages+CloudFlare.

We're now going to focus on the last three items: having a robots.txt, providing a sitemap.xml, and using all necessary meta tags.

Add a robots.txt to Gridsome

This is the easiest part. Add a file called static/robots.txt with the following contents:

Sitemap: https://mannes.tech/sitemap.xml

User-agent:*
Disallow: /legal
Disallow: /privacy

We use the robots.txt file to tell web crawlers what we allow them to index on our site. Find more about robots.txt on Neil Patel's blog.

A thought for EU-based developers: you can set your legal notice and privacy policy to Disallow. It only needs to be visible to your website visitors. When you disallow these pages via robots.txt, you can prevent Google from indexing these sites if you also add a noindex meta tag to the header of those pages.

Add a Sitemap to Gridsome

A sitemap is an xml file that contains all URLs on your website. Search engines use this information when crawling your website.

Adding a sitemap is straightforward.

First, add the sitemap plugin with yarn add @gridsome/plugin-sitemap.

Then, open your gridsome.config.js and use it like this:

// ...

module.exports = {
  siteName: 'Gridsome',
  plugins: [
// ...
    {
      use: '@gridsome/plugin-sitemap',
      options: {
        exclude: ['/privacy', '/legal']
      }
    }
  ],
// ...
}

You can find all documented options for the sitemap plugin here.

A convenient way to add meta tags to Gridsome

Meta tags tell search engines and social networks how to display your site on their page. They also give additional information like which Twitter card format should be used, which image should be rendered, and what your Twitter handle is.

First, we want to add some additional site information in our gridsome.config.js:

// ...

module.exports = {
  siteName: 'Awesome Blog',
  siteDescription: 'Blog about awesome lists, collections of resources around a specific technology.',
  siteUrl: process.env.DEPLOY_URL || 'https://blog.awesome',
  metadata: {
    twitter: {
      site: '@simon_mannes',
      creator: '@simon_mannes'
    }
  },
// ...
}

Next, we use these definitions in our default layout to set some general meta tags. Edit your src/layouts/Default.vue file:

<template>
  <!-- redacted for readability -->
</template>

<static-query>
  query {
    metadata {
      siteName
      siteDescription
      siteUrl
      author
      twitter {
        site
        creator
      }
    }
  }
</static-query>

<script>
// ...

export default {
// ...
  metaInfo () {
    return {
      meta: [
        { key: 'author', name: 'author', content: this.$static.metadata.author },
        { key: 'twitter:site', name: 'twitter:site', content: this.$static.metadata.twitter.site },
        { key: 'twitter:creator', name: 'twitter:creator', content: this.$static.metadata.twitter.creator }
      ]
    }
  }
}
</script>

Now, we create an SEO component that we can use as a mixin in our posts. Add a file called src/mixins/SEO.vue:

<static-query>
  query {
    metadata {
      siteName
      siteDescription
      siteUrl
      author
    }
  }
</static-query>

<script>
export default {
  metaInfo () {
    const siteUrl = this.$static.metadata.siteUrl
    const postPath = this.$page.post.path
    const image = this.$page.post.image?.path
    const imagePath = (image && `${siteUrl}${image.src}`) || ''

    return {
      title: this.$page.post.title,
      meta: [
        { key: 'description', name: 'description', content: this.$page.post.summary },
        { key: 'og:url', property: 'og:url', content: `${siteUrl}${postPath}` },
        {
          key: 'og:title',
          property: 'og:title',
          content: this.$page.post.title
        },
        {
          key: 'og:type',
          property: 'og:type',
          content: 'article'
        },
        {
          key: 'og:description',
          property: 'og:description',
          content: this.$page.post.summary
        },
        {
          key: 'og:image',
          property: 'og:image',
          content: imagePath
        },
        {
          key: 'og:image:width',
          property: 'og:image:width',
          content: (image && image.size.width) || ''
        },
        {
          key: 'og:image:height',
          property: 'og:image:height',
          content: (image && image.size.height) || ''
        },
        {
          key: 'twitter:description',
          name: 'twitter:description',
          content: this.$page.post.summary
        },
        {
          key: 'twitter:card',
          name: 'twitter:card',
          content: image ? 'summary_large_image' : 'summary'
        },
        {
          key: 'twitter:image',
          property: 'twitter:image',
          content: imagePath
        },
        {
          key: 'twitter:title',
          property: 'twitter:title',
          content: this.$page.post.title
        }
      ],
      script: [
        {
          type: 'application/ld+json',
          json: {
            '@context': 'http://schema.org',
            '@type': 'BlogPosting',
            description: this.$page.post.description,
            datePublished: this.$page.post.date,
            author: {
              name: this.$static.metadata.author
            },
            headline: this.$page.post.title,
            image: imagePath
          }
        }
      ]
    }
  }
}
</script>

In this file, we set the necessary meta:

  • title and description
  • og tags (OpenGraph) as information for social networks
  • twitter tags specifically for twitter, including the necessary definitions for twitter image cards

Note that we will add cover images in the next post and they will not be used at the moment.

Finally, add this mixin to your src/templates/BlogPost.vue, remove the old metaInfo property, and add path and summary to the page-query:

<template>
  <!-- redacted for readability -->
</template>

<page-query>
query Post ($path: String!) {
  post: blogPost (path: $path) {
    title
    date (format: "MMMM D, Y")
    content
    path
    summary
    tags {
      title
      path
    }
  }
}
</page-query>

<script>
import PostSeo from '../mixins/PostSEO'

export default {
  mixins: [PostSeo]
}
</script>

<style src="../css/markdown.css" />

But before we can use the summary field as our description meta tag, we need to add it to the front matter of our posts. Extend the front matter of your blog posts with a summary property like this:

---
slug: "third-post"
date: "2020-07-03"
title: "Code Coloring"
tags: ["frontend", "design", "code style"]
summary: "Banh mi authentic fashion axe affogato shoreditch umami bicycle rights keytar put a bird on it drinking vinegar pitchfork taxidermy."
---

## This should be a heading 2

I'm baby chartreuse knausgaard gastropub deep v mlkshk pickled crucifix chicharrones meggings.
...

When we now build our Gridsome project these are our meta tags on the blog post page for the Hipster Ipsum article:

<!DOCTYPE html>
<html data-html-server-rendered="true" lang="en" data-vue-tag="%7B%22lang%22:%7B%22ssr%22:%22en%22%7D%7D">
   <head>
      <title>Hipster Ipsum - Awesome Blog</title>
      <meta name="gridsome:hash" content="68f4394ecd32124a83710bacd50fdcb1466dd174">
      <meta data-vue-tag="ssr" charset="utf-8">
      <meta data-vue-tag="ssr" name="generator" content="Gridsome v0.7.15">
      <meta data-vue-tag="ssr" data-key="viewport" name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
      <meta data-vue-tag="ssr" data-key="format-detection" name="format-detection" content="telephone=no">
      <meta data-vue-tag="ssr" data-key="description" name="description" content="Listicle jianbing tbh sriracha tofu, waistcoat post-ironic copper mug williamsburg scenester.">
      <meta data-vue-tag="ssr" data-key="og:url" property="og:url" content="https://blog.awesome/blog/first-post/">
      <meta data-vue-tag="ssr" data-key="og:title" property="og:title" content="Hipster Ipsum">
      <meta data-vue-tag="ssr" data-key="og:type" property="og:type" content="article">
      <meta data-vue-tag="ssr" data-key="og:description" property="og:description" content="Listicle jianbing tbh sriracha tofu, waistcoat post-ironic copper mug williamsburg scenester.">
      <meta data-vue-tag="ssr" data-key="og:image" property="og:image" content="">
      <meta data-vue-tag="ssr" data-key="og:image:width" property="og:image:width" content="">
      <meta data-vue-tag="ssr" data-key="og:image:height" property="og:image:height" content="">
      <meta data-vue-tag="ssr" data-key="twitter:description" name="twitter:description" content="Listicle jianbing tbh sriracha tofu, waistcoat post-ironic copper mug williamsburg scenester.">
      <meta data-vue-tag="ssr" data-key="twitter:card" name="twitter:card" content="summary">
      <meta data-vue-tag="ssr" data-key="twitter:image" property="twitter:image" content="">
      <meta data-vue-tag="ssr" data-key="twitter:title" property="twitter:title" content="Hipster Ipsum">
      <meta data-vue-tag="ssr" data-key="author" name="author" content="Simon Mannes">
      <meta data-vue-tag="ssr" data-key="twitter:site" name="twitter:site" content="@simon_mannes">
      <meta data-vue-tag="ssr" data-key="twitter:creator" name="twitter:creator" content="@simon_mannes">
      <!-- ... -->
      <script data-vue-tag="ssr" type="application/ld+json">{"@context":"http://schema.org","@type":"BlogPosting","datePublished":"June 5, 2020","author":{"name":"Simon Mannes"},"headline":"Hipster Ipsum","image":""}</script>
      <!-- ... -->

When you deploy your blog (e.g. with Netlify) you can use one of the available meta validation tools to check how Google or Twitter render your site:

Next part: cover images for Gridsome posts

That’s it for this post on how to improve SEO on your Gridsome blog. In the next part of this series, we add cover images to our blog posts.