Skip to main content

Rendering SEO data

This guide covers how to render SEO data from the ButterCMS API in your frontend framework, including meta tags, Open Graph tags, and Twitter Cards.

Framework implementations

React / Next.js

Reusable SEO Component

You can use the next/head submodule to encapsulate modifications to the head element. Normally, you would have an SEO React component that would do this for you:
// components/seo.js
import React from 'react'
import Head from 'next/head'

function SEO ({ description, title }) {
  return (
    <Head>
      <title>{title}</title>
      <meta name='description' content={description} />
      <meta property='og:type' content='website' />
      <meta property='og:title' content={title} />
      <meta property='og:description' content={description} />
      <meta name='twitter:card' content='summary' />
      <meta name='twitter:title' content={title} />
      <meta name='twitter:description' content={description} />
    </Head>
  )
}

export default SEO
Then use this component in each of your pages:
// pages/index.js
import SEO from '../components/seo'

const Index = () => (
  <div>
    <SEO title='sample title' description='sample description' />
    <p>Hello Next.js</p>
  </div>
)

export default Index

Per-Page implementation with Open Graph

import Head from 'next/head';

function BlogPost({ post }) {
  const fullUrl = `https://yoursite.com/blog/${post.slug}`;

  return (
    <>
      <Head>
        {/* Open Graph */}
        <meta property="og:type" content="article" />
        <meta property="og:url" content={fullUrl} />
        <meta property="og:title" content={post.seo_title || post.title} />
        <meta property="og:description" content={post.meta_description} />
        <meta property="og:image" content={post.featured_image} />
        <meta property="og:site_name" content="Your Site Name" />

        {/* Twitter Card */}
        <meta name="twitter:card" content="summary_large_image" />
        <meta name="twitter:site" content="@YourHandle" />
        <meta name="twitter:title" content={post.seo_title || post.title} />
        <meta name="twitter:description" content={post.summary} />
        <meta name="twitter:image" content={post.featured_image} />
        <meta name="twitter:image:alt" content={post.featured_image_alt} />
      </Head>
      {/* Page content */}
    </>
  );
}

Angular

Meta tags like the page title and Open Graph tags play a big role in SEO. In Angular SPAs, you must update these tags dynamically as users navigate across different routes and make sure they’re ready when crawlers arrive. Angular provides the Title and Meta services to handle this:
import { Component, OnInit } from '@angular/core';
import { Title, Meta } from '@angular/platform-browser';

@Component({
  selector: 'app-about',
  templateUrl: './about.component.html',
})
export class AboutComponent implements OnInit {
  constructor(private title: Title, private meta: Meta) {}

  ngOnInit(): void {
    this.title.setTitle('About Us - My Angular App');
    this.meta.updateTag({
      name: 'description',
      content: 'Learn more about our company, team, and mission.'
    });
    this.meta.updateTag({
      property: 'og:title',
      content: 'About Us - My Angular App'
    });
    this.meta.updateTag({
      property: 'og:description',
      content: 'Learn more about our company, team, and mission.'
    });
  }
}

Jekyll

In Jekyll, each page is compiled from a markdown file. The header of the markdown file will contain the title and description variables that will be used in the template:
---
layout: post
title:  "Welcome to Jekyll!"
description: "Sample description"
date:   2019-08-28 00:22:09 +0530
categories: jekyll update
---

Some page content...
The default layout template uses these variables:
<head>
  <title>{{ page.title }}</title>
  <meta name="description" content="{{ page.description }}" />
  <meta name="og:title" content="{{ page.title }}" />
  <meta name="og:description" content="{{ page.description }}" />
</head>

Vue / Nuxt.js

<script setup>
const post = await useButter().post.retrieve('my-post-slug');

useHead({
  meta: [
    // Open Graph
    { property: 'og:type', content: 'article' },
    { property: 'og:url', content: `https://yoursite.com/blog/${post.slug}` },
    { property: 'og:title', content: post.seo_title || post.title },
    { property: 'og:description', content: post.meta_description },
    { property: 'og:image', content: post.featured_image },

    // Twitter Card
    { name: 'twitter:card', content: 'summary_large_image' },
    { name: 'twitter:site', content: '@YourHandle' },
    { name: 'twitter:title', content: post.seo_title || post.title },
    { name: 'twitter:description', content: post.summary },
    { name: 'twitter:image', content: post.featured_image },
  ]
});
</script>

Using ButterCMS API data for meta tags

When fetching content from ButterCMS, you’ll have access to SEO fields that you can use to populate your meta tags:
// Fetching a blog post
const response = await butter.post.retrieve('my-post-slug');
const post = response.data.data;

// Available SEO fields
const seoData = {
  title: post.seo_title,           // SEO-optimized title
  description: post.meta_description, // Meta description
  slug: post.slug,                 // URL slug
  featuredImage: post.featured_image, // For og:image
  featuredImageAlt: post.featured_image_alt
};
For Pages with an SEO component:
// Fetching a page with SEO component
const response = await butter.page.retrieve('*', 'my-page-slug');
const page = response.data.data;

// Access SEO component fields
const seoData = {
  title: page.fields.seo.title,
  description: page.fields.seo.description,
  ogImage: page.fields.seo.og_image,
  canonical: page.fields.seo.canonical_url
};

Creating an SEO Component in ButterCMS

Create a reusable SEO & Social component to give editors control over social sharing:
SEO & Social Component:
├── meta_title (Short Text) - max 60 chars
├── meta_description (Long Text) - max 160 chars
├── og_title (Short Text) - Falls back to meta_title
├── og_description (Long Text) - Falls back to meta_description
├── og_image (Media) - Recommended: 1200x630px
├── og_image_alt (Short Text)
├── twitter_title (Short Text) - Falls back to og_title
├── twitter_description (Long Text) - Falls back to og_description
├── twitter_image (Media) - Can differ from og_image
├── twitter_handle (Short Text) - Author's Twitter handle
└── canonical_url (Short Text) - Optional override
Then in your template:
function getSocialMeta(seoComponent, defaults) {
  return {
    ogTitle: seoComponent?.og_title || seoComponent?.meta_title || defaults.title,
    ogDescription: seoComponent?.og_description || seoComponent?.meta_description || defaults.description,
    ogImage: seoComponent?.og_image || defaults.image,
    twitterTitle: seoComponent?.twitter_title || seoComponent?.og_title || seoComponent?.meta_title || defaults.title,
    twitterDescription: seoComponent?.twitter_description || seoComponent?.og_description || defaults.description,
    twitterImage: seoComponent?.twitter_image || seoComponent?.og_image || defaults.image,
  };
}
Ready to add social sharing buttons to your site? See Add Social Sharing Buttons for a step-by-step guide.