Introduction

One of the most valuable things a website can provide is SEO (Search Engine Optimization). While a great deal of the SEO is found in the content that you can see in the output of your website, a great deal of it is also found in meta tags in the <head> section of your pages, which not rendered to the browser and can often be overlooked.

In this post, we'll take a look at how you can use a Gatsby plugin called gatsby-plugin-react-helmet to output metadata from Agility CMS to improve both SEO and the social sharing experience for your pages.

Get Started

I'll be using my own corporate website agilitycms.com as an example here, and you can find all the source code on GitHub.

GitHub logo agility / agility-website-gatsby

The Agility CMS Website - done in Gatsby

Create a free Agility CMS account

If you want to code this up yourself, you can create a free Agility CMS account and follow this tutorial that will take you step-by-step on getting started.

Install the Gatsby React Helmet Plugin

Our example relies on the Gatsby React Helmet plugin. Say that ten times fast! Then install it, as below.

npm install --save gatsby-plugin-react-helmet react-helmet

Why is Meta Data in the Head Tag so Important?

Title and Description

There are 2 main meta tags in the <head> section that you absolutely HAVE to include: the <title> tag and the <meta type="description"> tag. These tell a web crawler like Google what the title and description of your page is, and they are used in the actual search results output. The description should be about 160 characters, and while it isn't used by Google to rank your page, it will affect whether anyone actually clicks on it in a search result.

Controlling the Social Sharing Experience

Other tags also affect how your page appears in other tools, such a social sharing sites like Facebook, Twitter, and Linked In. These are controlled by outputting special OpenGraph tags and Twitter Card hints in the <head> section. As a developer, you have the ability to control how your page will appear by default when shared, and you can use the content that's pulled from the CMS to do it.

Google Structured Data

If your page represents something that has structured data, such as an Event, Rec with that date, presenters, and a registration link, you can provide that data to Google in the <head> tag with specialized JSON.

I'll show you how to do all this as well as how to validate it with easy online tools.

Step 1 - Hook Up The Head Tag

The first thing to do is to make sure you've got the ability to output to the <head> tag from your Gatsby site. You do that with the gatsby-plugin-react-helmet plugin. Now you have complete control over how your <head> tag is rendered when you use the React-Helmet component.

Agility CMS passes a page object that represents the data from CMS for the page that is currently being rendered. In this case, we're setting an seo property that object and sending it to our <SEO> component, which uses react helmet to set the head tag.

Here's a snippet from our page component.

const AgilityPage = ({ pageContext, data, location }) => {

    const viewModel = agilityUtils.buildPageViewModel({ pageContext, data, location });

    return (
        <LayoutTemplate page={ viewModel.page }>
            <SEO page={ viewModel.page }  />
            <GlobalHeader {... viewModel} />
            <main className="main">
                <AgilityPageTemplate {...viewModel} />
            </main>
            <GlobalFooter isLandingPage={viewModel.isLandingPage}/>
        </LayoutTemplate>
    );
}

Above, we're calling a utility function that builds the viewModel object that has our page object as a property, and we're passing it into the <SEO> component.

Here's a simplified version of the SEO component:

import React from 'react'
import { Helmet } from "react-helmet"

const SEO = ({ page }) => {

    return (
        <Helmet>
            <meta charSet="utf-8" />
            <title>{page.title}</title>
            <meta name="description" content={page.seo.metaDescription} />
        </Helmet>
    )
}

export default SEO;

Now, as long as our page object has a title and an seo.metaDescription property, we're good to go. Luckily, Agility CMS allows content editors to set this information directly for each page in the website, right in the CMS tool itself.

Step 2: Optimize for Social Sharing

If we are on a page that has a main image, such as a blog post, we'd like to have that image show up when the page is shared on social media, such as Facebook or Twitter.

Get the Data

Let's update our code to put that image object into the seo property of our page object so that we can use it in our <SEO> component.

Here, if I'm on page that represents a blog, I'm grabbing the image from that object. With Agility CMS, that object is automatically pulled into the dynamicPageItem prop.

//pull the SEO data from a blog post
if (dynamicPageItem.properties.definitionName === "BlogPost") {
    if (dynamicPageItem.customFields.postImage) {
        page.seo.image = dynamicPageItem.customFields.postImage;
    }
}

Now, we can update our SEO component to output the open graph and twitter image property if the image is present.

import React from 'react'
import { Helmet } from "react-helmet"

const SEO = ({ page }) => {

    return (
        <Helmet>
            <meta charSet="utf-8" />
            <title>{page.title}</title>
            <meta name="description" content={page.seo.metaDescription} />
            {page.seo.image &&
                <React.Fragment>
                    <meta property="og:image" content={page.seo.image.url} />
                    <meta property="twitter:image" content={page.seo.image.url} />
                    <meta property="twitter:card" content="summary_large_image" />
                </React.Fragment>
        </Helmet>
    )
}

export default SEO;

Validate It

There are some handy tools for validating your OpenGraph (Facebook) and Twitter sharing configurations. The only caveat here is that you have to deploy your website first, but you can test a staging or UAT version to get things right.

Twitter Validator

https://cards-dev.twitter.com/validator

Alt Text

Facebook OpenGraph Debugger

https://developers.facebook.com/tools/debug

alt text

Step 3: Structured Data for Search

If you really want to kick you SEO up a notch, you can add Structured Data to your <head> tag. This allows you to tell the crawler more about what kind of page it's indexing, and possibly allow for a richer search experience.

There are several different kinds of data types that Google allows you to customize, including definitions for Event, Recipe, Book, FAQ, How-to, and more. There's a reference page here that describes them all: https://developers.google.com/search/docs/guides/search-gallery

In this example, we'll take a look at how Agility CMS structures data for our Events, such as our webinars here https://agilitycms.com/events.

Each Event is defined in the Agility CMS backend with a Title, Date, Description, Image, URL, and a list of Presenters. This is what it looks like in the CMS itself - here's the Presenters tab of the Event:
alt text

Let's take this data and output the structure as outlined by Google for an Event.

Get the Data

Just like with our Article, Agility CMS gives us a dynamicPageItem prop that represents the Event. It's a fairly complex JSON string, so we assembly it as an object and then convert it to a string at the end.

const moment = require('moment')

//pull the SEO and strcutured data from an Event
if (dynamicPageItem.properties.definitionName === "Event") {

    if (dynamicPageItem.customFields.mainImage) {
        page.seo.image = dynamicPageItem.customFields.mainImage;
    }

    //pull out the presenters...
    const presenters = dynamicPageItem.customFields.presenters.map(p => {
        let img = null;
        if (p.customFields.image) {
            img = p.customFields.image.url + "?w=400"
        }
        return {
            "@type": "Person",
            "name": p.customFields.title,
            "image": img
        }
    });

    let validFrom = moment();
    let startTime = moment(dynamicPageItem.customFields.date);
    let endTime =  moment(startTime).add(1, "hours");

    //build the structural event data...
    let structData = {
        "@context": "https://schema.org",
        "@type": "Event",
        "name": dynamicPageItem.customFields.title,
        "startDate": startTime.toISOString(true),
        "endDate": endTime.toISOString(true),
        "eventAttendanceMode": "https://schema.org/OnlineEventAttendanceMode",
        "eventStatus": "https://schema.org/EventScheduled",
        "location": {
            "@type": "VirtualLocation",
            "url": dynamicPageItem.customFields.externalLink,
        },
        "image": [
            image.url,
            ],
        "description": dynamicPageItem.customFields.description,
        "offers": {
            "@type": "Offer",
            "url": canonicalUrl,
            "price": "0",
            "priceCurrency": "USD",
            "availability": "https://schema.org/InStock",
            "validFrom": validFrom.toISOString(true),
        },
            "performer": presenters,
        "organizer": {
            "@type": "Organization",
            "name": "Agility CMS",
            "url": "https://agilitycms.com"
        }
    }

    page.seo.structData = JSON.stringify(structData);
}

Now that we've got the structData property our page.seo object, we can output it in our <head> tag.

import React from 'react'
import { Helmet } from "react-helmet"

const SEO = ({ page }) => {

    return (
        <Helmet>
            <meta charSet="utf-8" />
            <title>{page.title}</title>
            <meta name="description" content={page.seo.metaDescription} />
            {page.seo.image &&
                <React.Fragment>
                    <meta property="og:image" content={page.seo.image.url} />
                    <meta property="twitter:image" content={page.seo.image.url} />
                    <meta property="twitter:card" content="summary_large_image" />
                </React.Fragment>}

            {page.seo.structData &&
                <script type="application/ld+json">{page.seo.structData}</script>}
        </Helmet>
    )
}

export default SEO;

That's it! Now we're ready to see if what we've done is working.

Validate It

Just like with the OpenGraph and Twitter, Google gives us a tool to validate your structured data. You can access it here: https://search.google.com/structured-data/testing-tool.

What's nice about this tool is that it allows you to copy and paste your HTML into it, so you can test a local build of your page. Note that you will have to run a full gatsby build to do this locally, as the gatsby develop doesn't give you the static HTML that you need.

You can also validate your page after deploying right from the Google Search Console. If Google has indexed the page, it will let you know if structured data was detected.

alt text

You can click through to see the details.

alt text

While adding structure data to your page doesn't guarantee that Google will show it in rich search results, as your overall site authority grows, you may start to see your structured data right in the google search results pane.

Keep Reading

You can read more about structured data over at Google's developer pages: https://developers.google.com/search/docs/guides/intro-structured-data.

Get rolling with Agility CMS and Gatsby

Agility CMS and Gatsby go great together. If you aren't using them already, you should be!

Get started in minutes: https://www.gatsbyjs.org/docs/sourcing-from-agilitycms/