Search Engine Optimization with GatsbyJS
Tags: seo, searchengineoptimization, gatsby, google
Published: 2021-07-02 23:12:13

---
title: Search Engine Optimization with GatsbyJS
published: true
date: 2021-07-01 00:00:00 UTC
tags: SEO,SearchEngineOptimization,GatsbyJS,Google
series: Building a developer blog with GatsbyJS
canonical_url: https://MihaiBojin.com/personal-site/seo-on-gatsbyjs
---

![Photo of S.E.O.](https://MihaiBojin.com/static/8a18e145d68024488b0348dcacd28234/0e5fe/merakist-l5if0iQfV4c-unsplash.jpg)<figcaption>"Photo by <a href="https://unsplash.com/@merakist?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Merakist</a> on <a href="https://unsplash.com/s/photos/seo?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a>"</figcaption>

🔔 This article was originally posted on my site, [MihaiBojin.com](https://MihaiBojin.com/personal-site/seo-on-gatsbyjs?utm_source=external&utm_medium=organic&utm_campaign=rss "MihaiBojin.com"). 🔔

* * *

This is an introductory post about Search Engine Optimization with Gatsby.

It covers a few basics that are easy to implement but will help with your SEO ranking in the long run.

* * *

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#google-search-console)Google Search Console

One piece of low-hanging fruit is to verify your domain with [Google's Search Console](https://search.google.com/search-console/welcome "Verify your domain in the Google Search Console").

Take 5 minutes to do it; you will get helpful information, for example:

[![Google Search Console screenshot](https://MihaiBojin.com/static/3277529ebfcdc734b12724e5943f85f4/e9beb/google-search-console.png "Mobile usability")](https://MihaiBojin.com/static/3277529ebfcdc734b12724e5943f85f4/60708/google-search-console.png)

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#canonical-links)Canonical links

Every page should define a canonical link. These are useful because they help search engines such as Google figure out the original content, routing all the 'juice' to that one page.

Consider the following URLs:

```
- http://site.com/article
- http://www.site.com/article
- https://site.com/article
```

The same content can be access at multiple URLs. However, Google wouldn't know what page holds the original content, so it would most likely split the ranking between a few of these pages and mark most of them as duplicates. This is highly unpredictable and undesirable. A better approach is to define a canonical link, helping search engines rank a single URL, the one you choose.

You can define a canonical link by including the following tag in the site's head:

```
<link rel="canonical" href="https://MihaiBojin.com/"/>
```

(where `href` is the URL you choose to represent your original content.)

This approach is highly effective when you syndicate your content on other sites. For example, if you did so on Medium, Google would most likely rank it as the original due to the site's higher overall ranking, leading to poor SEO impact on your domain.

There's an effortless way to add canonical links in Gatsby: using the [gatsby-plugin-react-helmet-canonical-urls](https://www.gatsbyjs.com/plugins/gatsby-plugin-react-helmet-canonical-urls/) plugin.

Install it with `npm install --save gatsby-plugin-react-helmet gatsby-plugin-react-helmet-canonical-urls`.

Then add the following plugin to your `gatsby-config.js`:

```
module.exports = {
  plugins: [
    {
      resolve: `gatsby-plugin-react-helmet-canonical-urls`,
      options: {
        siteUrl: SITE_URL,
      },
    },
  ],
};
```

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#sitemap)Sitemap

Google will gradually discover and index your site's pages over time. You can define a sitemap to help Google (and other search engines) find **all** your pages. This process does not ensure faster indexing, and in the early days of your site, it [also doesn't make much of a difference](https://twitter.com/mihaibojin/status/1410349406944116745 "Google is slowly indexing my site's pages"), but it will matter more as your site grows!

First, install the [gatsby-plugin-sitemap](https://www.gatsbyjs.com/plugins/gatsby-plugin-sitemap/#example) plugin.

```
npm install gatsby-plugin-sitemap
```

Then you will need to add some custom configuration in `gatsby-config.js`. When I initially configured this plugin, this step took me a long time to get right. Part of that was caused [by a bug](https://twitter.com/mihaibojin/status/1394440704194191361 "Inability to generate the sitemap XMLs in a non-root path") that's since been fixed.

```
module.exports = {
  plugins: [
    {
      resolve: 'gatsby-plugin-sitemap',
      options: {
        output: '/sitemap',
        query: `
        {
          site {
            siteMetadata {
              siteUrl
            }
          }
          allSitePage(
            filter: {
              path: { regex: "/^(?!/404/|/404.html|/dev-404-page/)/" }
            }
          ) {
            nodes {
              path
            }
          }
        }
        `,
        resolvePages: ({ allSitePage: { nodes: allPages } }) => {
          return allPages.map((page) => {
            return { ...page };
          });
        },
        serialize: ({ path }) => {
          return {
            url: path,
            changefreq: 'weekly',
            priority: 0.7,
          };
        },
      },
    },
  ],
};
```

If you're wondering why we're loading a `siteUrl`, it is required, as explained in the [official docs](https://www.gatsbyjs.com/plugins/gatsby-plugin-sitemap/#api-reference "siteMetadata.siteUrl is required by the gatsby-plugin-sitemap plugin").

Check your sitemap locally by running `gatsby build && gatsby serve` and accessing [http://localhost:9000/sitemap/sitemap-index.xml](http://localhost:9000/sitemap/sitemap-index.xml). If everything is okay, publish your sitemap and then submit it to the [Google Search Console](https://search.google.com/search-console/welcome "Submit your sitemap for processing, in the Google Search Console").

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#robots)Robots

[Robots.txt](https://developers.google.com/search/docs/advanced/robots/intro "Web robots") is an old concept; the `robots.txt` file tells search engines what URLs are okay to access (and subsequently index) on your site.

If you're looking for an automated way to generate this file, [gatsby-plugin-robots-txt](https://www.gatsbyjs.com/plugins/gatsby-plugin-robots-txt/ "GatsbyJS robots plugin") is your friend!

Install the plugin by running `npm install --save gatsby-plugin-robots-txt` and add the following to your `gatsby-config.js`:

```javascript
module.exports = {
  plugins: [
    {
      resolve: 'gatsby-plugin-robots-txt',
      options: {
        host: process.env.SITE_URL,
        sitemap: process.env.SITE_URL + '/sitemap/sitemap-index.xml',
        policy: [
          {
            userAgent: '*',
            allow: '/',
            disallow: ['/404'],
          },
        ],
      },
    },
  ],
};
```

Setting up the plugin as above will generate a `/robots.txt` file that specifies the options listed above.

If, however, you're looking for a simpler approach, skip all of the above and create `/static/robots.txt`, with the same contents.

```txt
User-agent: *
Allow: /
Disallow: /404
Sitemap: https://[SITE_URL]/sitemap/sitemap-index.xml
Host: https://[SITE_URL]
Make sure to replace SITE_URL with the appropriate value!
```

There is one disadvantage of the latter, in that you will have to remember to update the SITE_URL in this file. This is unlikely to be a problem in practice, but still... using the plugin and relying on a globally defined `SITE_URL` is less hassle in the long run.

Note: In Gatsby, any file created in the `/static` directory will be copied to and included as a static file on your site.

You may have noticed the `Sitemap` directive. That allows letting crawlers know where they can find your sitemap index. Earlier in this article, we accomplished that using the Google Search Console, which is the better approach. However, there are other search engines except for Google and having this directive means they can all find and process your sitemap.

## [<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewbox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>](#seo-with-gatsby)SEO with Gatsby

We're finally at the end - albeit this is the trickiest part since it requires an understanding of how Gatsby does SEO.

If you have no experience with that, I recommended reading the [Gatsby Search Engine Optimization page](https://www.gatsbyjs.com/tutorial/seo-and-social-sharing-cards-tutorial/) (don't worry about Social Cards for now).

This article won't go into advanced SEO tactics (mostly because I'm no SEO expert).

In time, as I write more content and I learn new search engine optimization tips and tricks, I will update this page.

There is, however, a minimal set of configurations any site owner should do, the bare minimum, if you will:

title, description, and keywords meta tags that represent your content
and a canonical link (explained above)

If you search the [Gatsby Starters library](https://www.gatsbyjs.com/starters/ "Search for GatsbyJS starters"), you will find many implementations and slightly different ways of achieving this goal.

Here's how I implemented it. I don't claim this is better or provides any advantages - it simply does the job!

```javascript
import * as React from "react";
import PropTypes from "prop-types";
import { Helmet } from "react-helmet";

import { useSiteMetadata } from "../hooks/use-site-metadata";

function Seo({ title, description, tags, canonicalURL, lang }) {
  const { siteMetadata } = useSiteMetadata();

  const pageDescription = description || siteMetadata.description;
  const meta = [];
  const links = [];

  if (tags) {
    // define META tags
    meta.push({
      name: "keywords",
      content: tags.join(","),
    });
  }

  if (canonicalURL) {
    links.push({
      rel: "canonical",
      href: canonicalURL,
    });
  }

  return (
    <Helmet
      htmlAttributes={{
        lang,
      }}
      // define META title
      title={title}
      titleTemplate={siteMetadata?.title ? `%s | ${siteMetadata.title}` : `%s`}
      link={links}
      meta={[
        // define META description
        {
          name: `description`,
          content: pageDescription,
        },
      ].concat(meta)}
    />
  );
}

Seo.defaultProps = {
  lang: `en`,
  tags: [],
};

Seo.propTypes = {
  title: PropTypes.string.isRequired,
  description: PropTypes.string.isRequired,
  tags: PropTypes.arrayOf(PropTypes.string),
  canonicalURL: PropTypes.string,
  lang: PropTypes.string,
};

export default Seo;
```

This code uses `react-helmet` to define the specified tags.

You might have noticed that it uses a [react hook](https://reactjs.org/docs/hooks-intro.html "React Hooks Introduction"), `useSiteMetadata`.  I learned this trick from [Scott Spence](https://scottspence.com/2019/03/24/custom-gatsby-metadata-hook/ "Gatsby site metadata react hook").

My code looks like so:

```javascript
import { useStaticQuery, graphql } from 'gatsby';

export const useSiteMetadata = () => {
  const { site } = useStaticQuery(
    graphql`
      query SiteMetaData {
        site {
          siteMetadata {
            title
            description
            author {
              name
              summary
              href
            }
            siteUrl
          }
        }
      }
    `,
  );

  return { siteMetadata: site.siteMetadata };
};
```

Don't forget to define the required metadata in `gatsby-config.js`:

```javascript
module.exports = {
  siteMetadata: {
    title: `Mihai Bojin's Digital Garden`,
    description: `A space for sharing important lessons I've learned from over 15 years in the tech industry.`,
    author: {
      name: `Mihai Bojin`,
      summary: `a passionate technical leader with expertise in designing and developing highly resilient backend systems for global companies.`,
      href: `/about`,
    },
    siteUrl: process.env.SITE_URL,
  }
};
```

And that's it - you will now have basic SEO for your GatsbyJS site, by including this component into every article page, e.g.:

```javascript
<Seo title={} description={} tags={} canonicalURL={} />
```

Typically, these values would be populated manually for static pages, or passed via frontmatter for blog posts.

For example, to populate canonical links I use a function to generate the appropriate value: frontmatter for blog posts or the page's path as a default.

```javascript
function renderCanonicalLink(frontmatter, siteUrl, location) {
  return frontmatter?.canonical || (siteUrl + location.pathname);
}
```

This concludes my introductory post about Search Engine Optimization in GatsbyJS.

If you'd like to learn more about this topic, please [add a comment on Twitter](https://twitter.com/mihaibojin/status/1410765993031385089) to let me know! 📢

Thank you!

* * *

If you liked this article and want to read more like it, [please subscribe to my newsletter](https://motivated-founder-807.ck.page/db1cf284bf "newsletter link"); I send one out every few weeks!