Improving SEO and Social Sharing with Gatsby and Agility CMS
Tags: webdev, seo, gatsby, cms
Published: 2020-05-27 15:55:04

## 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](https://agilitycms.com) as an example here, and you can find all the source code on GitHub.

{% github agility/agility-website-gatsby no-readme %}

#### Create a free Agility CMS account
If you want to code this up yourself, you can create a [free Agility CMS account](http://agilitycms.com/free) and follow [this tutorial](https://www.gatsbyjs.com/docs/agility-cms/getting-started/) 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.
```shell
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](https://developers.google.com/search/docs/guides/intro-structured-data) 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](https://www.gatsbyjs.org/packages/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.

```javascript
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:

```javascript
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.
```javascript
//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.

```javascript
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](https://dev-to-uploads.s3.amazonaws.com/i/iklcrmmmzcml7kej6jgt.png "Twitter Card Validator")
](https://cards-dev.twitter.com/validator)

#### Facebook OpenGraph Debugger
https://developers.facebook.com/tools/debug
[
![alt text](https://dev-to-uploads.s3.amazonaws.com/i/baipa714ca4s11z70yy9.png "Facebook OpenGraph Debugger")
](https://cards-dev.twitter.com/validator)

## 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](https://dev-to-uploads.s3.amazonaws.com/i/t525ky9wr3kjzcr8efxe.png "Agility CMS Screenshot - Editing an Event - Presenters Tab")


Let's take this data and output the structure as [outlined by Google](https://developers.google.com/search/docs/data-types/event) 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.

```javascript
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.

```javascript
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](https://dev-to-uploads.s3.amazonaws.com/i/x8uu8bbub8fq0lio7yqy.png "Google Search Console - Event Detected")

You can click through to see the details.

![alt text](https://dev-to-uploads.s3.amazonaws.com/i/0528hlay7dlyr3fufwp9.png "Google Search Console - Event Detected")

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/