Adding Open Graph and Twitter Meta Tags to a GatsbyJS Website

Recently I have been sharing more of my work and posts over on my Twitter (side note, I highly recommend checking it out.) Anyway, as anyone using Twitter will tell you, tweets with a visual aspect to them tend to perform better than ones that are purely text; so naturally, this led me to the conclusion that Twitter Cards would be a good addition. So, in this post, I wanted to give a quick tutorial on how to add Open Graph and Twitter meta tags to a GatsbyJS website to allow for Twitter to create Twitter Cards off the back of any link being shared on the platform.

Twitter Cards

To allow for Twitter Cards to be created off the links on your website you need to drop a few meta tags into the head of your pages. Below are the meta tags I've added to the pages on my website to facilitate this.

<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content={twitterUsername} />
<meta name="twitter:creator" content={twitterUsername} />
<meta name="twitter:url" content={seo.url} />
<meta name="twitter:title" content={seo.title} />
<meta name="twitter:description" content={seo.description} />
<meta name="twitter:image" content={seo.image} />

The above is an excerpt from my SEO component which can be fully viewed here.

That's how you do it but how do you implement it in GatsbyJS, in particular there how do you cater for pages that haven't even been created yet like blog posts?

The SEO Component

As per GatsbyJS's own documentation, I choose to go for the SEO component method of implementing all of the SEO related info onto my website.

Essentially, this is a component that you place on every page and it ensures all the correct meta tags and info is loaded onto the page when it is statically generated or viewed by a user.

For example, on my blog page the component looks like this:

<SEO post={{
    slug: path,
    title: pageTitle,
}}
/>

Now, this is great for an individual one-off page like the blog page but a big part of my website and most websites for that matter is generated content like the notes and blog posts, to ensure each of those pages gets the meta tags loaded, we need to add the SEO component to the template pages like so:

<SEO post={{
    slug: path,
    title,
    description: description === '' ? excerpt : description,
    image: imagePath,
    article: true,
    date: plainDate,
}}
/>

As you can see for the generated content there are a lot more props passed to the SEO component, this is all information required to tailor the meta tags specifically to that piece of content rather than relying on defaults being imported into the SEO component.

To get a better grasp on how the SEO component method works, let's break the one I use for my website down into its main segments. If you are interested in seeing my full SEO component setup you can see it here.)

My SEO component can be broken down into 3 segments:

  1. Data imports
  2. The SEO object
  3. Meta tags

Let's go through each one to see how they work along with how they work together.

Data imports

The first thing I do is destructure out the values from the props passed to the SEO component on each page, the pathname from useLocation(), and a bunch of values from my gatsby-config.js file via a custom hook useSiteMetadata() these act as my default values when a specific field has not been passed to the SEO component, in the case of some of the values from gatsby-config.js they match the values passed as props so I prefix them with 'default'.

const { title, description, image, slug, article, date } = post;
const { pathname } = useLocation();
const { title: defaultTitle, description: defaultDescription, author, siteUrl, twitterUsername, image: defaultImage } = useSiteMetadata();

This then allows me to build my SEO object.

The SEO object

The SEO object is just a standard object with various properties relating to the different pieces of information required for the meta tags, the main function of this object is to decide if I need to use the props passed or the defaults.

const seo = {
    title: title || defaultTitle,
    description: description || defaultDescription,
    image: `${siteUrl}${image || defaultImage}`,
    url: `${siteUrl}${pathname}`,
  };

const canonical = pathJoin(siteUrl, slug);

For each of the properties, the first thing checked is if there is a value passed as props, if not it falls back to the defaults. This is why standalone pages like the blog page have fewer props being passed than the generated pages because on the standalone pages the default values are relevant where on the generated pages I want to use information specific to that page.

Finally, although not within the SEO object itself I create a canonical link to use for the canonical meta tag to ensure that if I cross-post any of my posts Google knows this is the original source.

Meta tags

My site's meta tags are broken up to 3 sections:

  • Generic tags
  • Open Graph tags
  • Twitter-specific tags

Generic tags

In this section I set meta tags which aren't related to Open Graph or Twitter, some of these do however act as fallback values for Open Graph / Twitter should their specific tags fail for some reason.

<html lang="en" />
<title>{seo.title}</title>

{/* Meta Tags */}
<link rel="canonical" href={canonical} />
<meta name="description" content={seo.description} />
<meta name="image" content={seo.image} />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="Content-Type" content="text/html; charset=UTF-8" />
<meta name="generator" content="Coner Murphy on Gatsby!" />

Open Graph Tags

In the second section I set the meta tags that are specifically for Open Graph:

{/* Open Graph */}
<meta property="og:url" content={seo.url} />
{article ? <meta property="og:type" content="article" /> : <meta property="og:type" content="website" />}
<meta property="og:title" content={seo.title} />
<meta property="og:description" content={seo.description} />
<meta property="og:image" content={seo.image} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="628" />
<meta property="og:locale" content="en_GB" />
<meta property="og:site_name" content="Coner Murphy" />
{date ? <meta property="article:published_time" content={new Date(date).toISOString()} /> : null}

The interesting ones here are the 'type' and 'published_time' meta tags as they are dependent on the values being passed in. This allows me to show all generated content from my notes posts and blog posts as an 'article' and everything else as a 'website', also if it is an article then I can set the published date and time.

Twitter Tags

Finally, I have a bunch of tags specifically geared towards Twitter and this is what creates the Twitter Cards for links from my website.

{/* Twitter Tags */}
{article ? <meta name="twitter:card" content="summary_large_image" /> : <meta name="twitter:card" content="summary" />}
<meta name="twitter:site" content={twitterUsername} />
<meta name="twitter:creator" content={twitterUsername} />
<meta name="twitter:url" content={seo.url} />
<meta name="twitter:title" content={seo.title} />
<meta name="twitter:description" content={seo.description} />
<meta name="twitter:image" content={seo.image} />

You get a few different types of Twitter Cards, you can check them out here. I personally choose to go for the 'summary_large_image' card for all of my blog and notes posts and then the 'summary' card for all other pages.

Overall, my SEO component works by taking in the data from the calling page and the defaults from the gatsby-config file. Then checking if any fields were missing from the passed props and then filling the gaps with defaults. Then finally, passing the data to the meta tags to be added to the page.

Implementing the SEO Component

When it comes to implementing the SEO component and therefore adding all of the meta tag goodness to each page there is a couple of ways you can do it.

Wrapping the Layout Component

You could wrap every page in the SEO component using the Layout.js file like so:

export default function Layout({children}) {
    return (
        <div>
            <SEO>
                {children}
            </SEO>
        </div>
    )
}

But, this method has its shortcomings depending on how you implement your Layout.js file into your project you will make passing props to the SEO component a massive pain. So, this method is okay if you don't have much or any generated content like blog posts to customise your meta tags to. But, if you do then you'll want something a bit more flexible at your disposal.

Implementing on each page

For my site and sites where there is a lot of generated content to cater for, I lean towards the approach of adding a separate SEO Component to each page's individual file. This way I can directly pass as many props as I need to straight to the file without having to jump through any hoops.

For example, in my blog post I can do the following:

export default function Blog() {
    return (
        <>
        <SEO post={{
            slug: path,
            title: pageTitle,
            }}
        />
        {/* Inset Content here */}
        </>
    )
}

And, when I want to pass more props to have more custom tags I can do something like my blog posts:

export default function BlogTemplate() {
    return (
        <>
        <SEO post={{
          slug: path,
          title,
          description: description === null ? excerpt : description,
          image: imagePath,
          article: true,
          date: plainDate,
        }}
      />
        {/* Inset Content here */}
        </>
    )
}

You may be thinking this still leaves the issue of adding the meta tags for all user-generated content though?

But, GatsbyJS gives us a helping hand with this problem. While individual pages like the blog page need to have the SEO component individually added to them when it comes to generated content we just add the SEO component to the template file we define to be used when generating that content like I briefly show above.

Then when each post is created into the page, the props will be passed to the SEO component every time and in turn, every page generated will have all the correct meta tags implemented on it and ready to be consumed by users and Twitter alike.

Summary

In this post we have covered:

  • Twitter Cards
  • Meta and Twitter Tags
  • Implementing an SEO component on a GatsbyJS website.

If you'd be interested in seeing a video showing how to implement this, please let me know over on Twitter. Also, if you have questions or issues please let me know also on Twitter and I'd be happy to help.