Lewis Tyler

Simple, powerful Statamic SEO in 10 minutes

Statamic is a Content Management System (CMS) built on Laravel, which makes it a good solution for Laravel projects requiring a CMS.

Out of the box, Statamic doesn’t offer any controls for SEO, so if you have a client who expects this you’ll have to roll your own (or pay for their premium SEO add-on).

That’s not an issue though as it’s easy to add valid SEO markup and metadata throughout your Statamic site.

So your layout.antlers.html might look something like the code block below. I’ve added two partials on lines 9 and 10 where our SEO code will live, all nice and neat.

<!-- resources/views/layout.antlers.html -->

<!doctype html>
<html lang="{{ site:short_locale }}">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        {{ partial:seo_tags }} // description, social card meta tags
        {{ partial:json_ld }} // schema.org json+ld
        <link rel="stylesheet" href="{{ mix src='css/tailwind.css' }}">
        <script src="{{ mix src='/js/site.js' }}"></script>
    </head>
    <body>
        {{ partial:nav }}
        <main>
            {{ template_content }}
        </main>
        {{ partial:footer }}
        {{ partial:extras }}
    </body>
</html>


Let’s quickly set up a few of extra fields for entry of our SEO data:

# resources/fieldsets/seo.yaml
-
title: SEO
fields:
  -
    handle: seo_title
    field:
      antlers: false
      display: Description
      type: text
      icon: text
      listable: hidden
  -
    handle: seo_description
    field:
      antlers: false
      display: Description
      type: textarea
      icon: textarea
      listable: hidden
  -
    handle: seo_image
    field:
      antlers: false
      display: SEO Image
      type: assets
      icon: assets

This Fieldset can then be attached to any Collection where SEO is important e.g. Pages, Posts, Portfolio. Do this via the relevant Blueprint for that Collection. In the screenshot below I created a new tab, ‘SEO’, and hit ‘Link Existing’ to attach the Fieldset.

[img]

When it comes to SEO, the two crucial tags are <title> and <meta name="description">.

I handle those in _seo_tags.antlers.html which also includes some social sharing goodies.

<!-- resources/views/_seo_tags.antlers.html -->

<title>{{ seo_title ?? seo_title ?? title }} — {{ settings:site_name }}</title>
<meta name="description" content="{{ seo_description ?? seo_description ?? content | strip_tags | entities | trim | safe_truncate:160 }}">
// social
<meta name="twitter:card" value="summary">
<meta property="og:title" content="{{ seo_title ?? seo_title ?? title }}" />
<meta property="og:type" content="article" />
<meta property="og:url" content="{{ page:permalink }}" />
{{ if seo_image || featured_image }}
<meta property="og:image" content="{{ seo_image ?? seo_image:url ?? featured_image }}" />
{{ /if }}
<meta property="og:description" content="{{ seo_description ?? seo_description ?? content | strip_tags | trim | entities | safe_truncate:160 }}" />

So as well as the title and description we’ve also got the basic, bare minimum Open Graph tags for enabling rich content when links are shared on websites or apps.

There are also fallbacks for our seo_title and seo_description using the default title and content fields and with the description we’re making use of some Statamic modifiers to keep our output clean.

This example also assumes you’re using a featured_image field for your post or page main image, with the SEO Image we set up obviously taking precedent. (You could also go one step further and add a global fallback image but this solution should be fine for most).

Finally, our JSON-LD. This is by no means essential but it is beneficial and allows you to share more detailed information about your pages with search engines.

My scenario is a common one - a corporate website with a blog. Occasionally the client uses authored content, otherwise the content is published as the organisation.

<!-- resources/views/_json_ld.antlers.html -->

<script type="application/ld+json">
{
  "@context": "https://schema.org/",
  "@type": "{{ if collection:handle === 'posts' }}blogPost{{ else }}Corporation{{ /if }}",
  "name": "{{ title }}",
  "author": {
    "@type": "{{ if author }}Person{{ else }}Organization{{ /if }}",
    "name": "{{ if author }}{{ author:title }}{{ else }}CompanyName{{ /if }}"
  },
  "datePublished": "{{ date format="Y-m-d" }}",
  "description": "{{ description ?? description ?? content | strip_tags | trim | entities | safe_truncate:160 }}"
}
</script>

You can see I’m making use of an author field in a conditional to switch between Person and Organization or to display the authors name versus the company name.

Check out schema.org for an official list of valid properties or Steal our JSON-LD for a friendlier explanation of the various properties and battle-tested strategies.

Don’t forget to check the validity of your JSON+LD with something like Google’s Structured Data Testing Tool.