How to handle SEO with a Headless CMS is an extremely common question we encounter. To simplify this, here’s how we handle SEO for our own website using our CMS.
To clarify, this post covers the operational aspects of implementing SEO in your projects. For a general understanding of what to consider when setting up a new project, refer to our guides on Headless CMS and SEO Best Practices and Headless CMS and SEO: Technical Best Practices.
SEO beyond website buildersAnchor
When setting out to build websites using a combination of well-known solutions like WordPress + Yoast, getting up to speed with operationally managing SEO is a very familiar concept. Most traditional CMSs allow you to edit site metadata, such as titles and descriptions, out of the box. Since a Headless CMS has no control over the final frontends or how the content is rendered, this functionality has to be added in, ideally, once the technical foundations are set.
Why not just continue using a legacy website builder?
There are well-known SEO benefits of migrating from a Legacy CMS to a Headless CMS in regard to page performance, security, user experience, and delivering content to multiple platforms - all of which directly and indirectly impact your SEO. Since a headless CMS will not necessarily give you the plug-and-play simplicity of installing a plugin to manage SEO, you need to follow a few best practices and adjust your implementation to go beyond what monolithic CMS would offer you.
How we handle SEOAnchor
We currently maintain all the content for our website from within GraphCMS itself. We build our site using a Static Site Generator (Gatsby), and deploy new iterations to Vercel using webhooks.
All the content related to SEO is handled within GraphCMS, along with image optimizations. The nuances for structured data, redirects, our sitemap, and robots.txt are handled within the codebase, usually via Gatsby plugins.
SEO as a modelAnchor
Since the concept of simple plug-and-play plugins, like Yoast, does not exist in the Headless world, we opted to run our operational SEO as a model within GraphCMS. All the required fields are set as an SEO model, which can have relations to a variety of pages, posts, and resources. In practice, this is what our SEO model currently looks like.
Using this approach, we enforce the correct SEO attributes in place by defining the metadata as a requirement in our data model when content creators create or edit new data.
The meta-title
, description
, and focus keywords
are taken as text strings to reflect accordingly when the website is crawled and indexed.
Our OG Images are generated programmatically using Vercel's OG Image as a Service, although having the option to manually override each asset as a fallback option for greater control is made possible by including an asset
field in the model. OG and meta info is populated to each page with each build, ensuring that all content has SEO attributes as required, and are up to date whenever the website is rebuilt.
Pages and posts can be connected to this SEO model as required, ensuring that every piece of content has its corresponding SEO attributes attached. We’ve kept the relation optional
, so if there are no tweaks needed, then the website inherits default SEO values from the page or post itself.
For certain pages (especially resources behind a lead wall), having a Boolean field for a _noindex
tag (or a _nofollow
, or any other SEO attributes as required) allows our content editors to exercise greater control over the content they're publishing. For even further granularity, the options are endless - we opted to include the ideal crawling frequency and sitemap priority as well.
Taking this approach at GraphCMS allows us to make sure all SEO attributes for content are easy to create, edit, and modify on the fly, and might resemble the familiarity of adding SEO attributes via plugins in the majority of legacy CMS platforms.
SEO FieldsAnchor
Now that we’ve seen how the concept of SEO as a model works, let’s get into the details of how we use them at GraphCMS.
Title: A Single line text
string field to establish the page/post meta title.
Description: A Multi line text
string field to establish the page/post meta description.
Keywords: A Single line text
string field with multiple options to define the focus keywords.
Priority: A Float
field with values ranging from 0.0 to 1.0 to set our preferred priority per page
Frequency: A Dropdown
enumeration field to indicate how often we change this page for an ideal recrawl.
noindex: A Boolean
toggle to define whether or not this page should be indexed within SERPs.
Image: An Asset
field to upload any corresponding image.
Relations: A Reference
field to several pages and posts, to establish a one-to-one content relationship between an SEO model and its corresponding page/post.
SEO for AssetsAnchor
While we’ll roll out a new post specifically catered towards Image SEO with a Headless CMS, here’s a quick peek into how we handle our assets at GraphCMS.
Prior to any images being uploaded to the CMS, we ensure that our files have descriptive names and are pre-optimized. While it's always recommended to compress large files with services like Caesium or TinyPNG before uploading them - GraphCMS’s Assets API allows for granular transformations to resize, modify, and optimize images when being queried.
For example, this very post renders its featured image with resize=w:960,fit:crop/quality=value:75/output=format:webp/compress/
to have the final output transformed from PNG
to WebP
, and optimized for greater performance.
More operationally, the GraphCMS UI allows for greater flexibility in handling images and other assets for SEO. Similar to how the SEO component was explained, the asset model itself can be extended to include any attributes that you would need.
Here are a few quick wins on how to extend the asset model with custom fields:
Caption: A Rich text field
to caption and credit images, and have them rendered below assets.
Resize: A Number
field to set values, so your final images queried are rendered at predefined values like 25%, 50%, or 75%.
Localized Alt Text: A Single line text
field with localization enabled, to define the alt text
of your images in several languages depending on your business model.
Depending on the complexity you require and the resources at hand, there are several possibilities to further expand on this depending on your use case - max dimensions, format switches, mobile-specific images - the options are endless.
Resolving SEO as a react ComponentAnchor
Finally, on the frontend, the added attributes are then compiled on the next website build prior to deploying as part of an "SEO" component.
import React from 'react';import Helmet from 'react-helmet';import useSiteMetadata from '../hooks/useSiteMetadata';const SEO = ({ children, title, subTitle, meta }) => {const {description: metaDescription,keywords: metaKeywords,noindex,title: metaTitle,image,} = meta || {};const {keywords,title: siteTitle,titleTemplate,siteUrl,defaultImage,twitter,} = useSiteMetadata();const pageTitle = metaTitle || title;const pageDescription = metaDescription || subTitle;const pageKeywords = metaKeywords || keywords;const ogImage = image?.url || defaultImage;return (<HelmethtmlAttributes={{ lang: 'en' }}defaultTitle={siteTitle}titleTemplate={titleTemplate}><title>{pageTitle}</title><meta name="description" content={pageDescription} /><meta name="keywords" content={pageKeywords} /><meta property="image" content={ogImage} /><meta property="og:url" content={siteUrl} /><meta property="og:title" content={pageTitle} /><meta property="og:description" content={pageDescription} /><meta property="og:site_name" content={siteTitle} /><meta property="og:image" content={ogImage} /><meta name="og:type" content="website" /><meta name="twitter:site" content={`@${twitter}`} /><meta name="twitter:title" content={pageTitle || siteTitle} /><meta name="twitter:card" content="summary_large_image" /><meta name="twitter:image:src" content={ogImage} />{!!noindex && <meta name="robots" content="noindex" />}{children}</Helmet>);};export default SEO;
And finally, when the time comes to build pages prior to deploying, the job is done for us with pages requesting any added meta attributes before our sitemap is updated using the Gatsby Sitemap Plugin.
meta: seo {...seoData}