Generating Open Graph images with Gatsby

Since reach is an important aspect for every blog, I want to make sure my social card previews are displayed properly and prominently when someone shares my post.

I currently have a boilerplate social image for all of the blog posts.

Current boilerplate implementation for blog posts

It works well, but not well enough for my liking. I want something different for each blog post, and the thumbnail should provide something useful other than branding purposes.


Design

The design should be simple because I don’t like over complicating things more than they need to be. The most common layout I have seen with Open Graph images in personal blogs includes a logo, some background patterns, a title, an excerpt, and a publish date. But we’re not going to do all that.

The only reason why I want an Open Graph social image is that I want the tappable area to be larger than an ordinary link.

Small Twitter card design
Large Twitter card design

Looking at the current Facebook and Twitter card design for links, the title, website domain and excerpt are already included in the card description.

The information shown on Twitter and Facebook card designs

The data I plan on repeating in the image design is the title. Because when people are looking at an article in their timeline, they’re reading, and so you need to give people things to read. Now we know we’re only working with:

  1. A logo
  2. The title of the article

It was time to figure out all the technicalities. The first thing that comes to mind is the image size. Thankfully, someone on the internet already did the research. It seems like the perfect ratio is 1.9:1, and the size is 1200 x 630. I’ll double the image size just in case, and because I have a Retina display, I like my images crisp.

Something I noticed when I was doing the mockup for the image is that sometimes, the text would flow one or two words onto a second line, which looks absolutely horrendous.

Long blog post title leaving a widow

I can reduce the text size so that the word “website” would fit on a single line. Or I can just resize the text box so that the line will break where it doesn’t leave any widows.

I figured that once again someone on the internet probably already tried to solve this. And they did. The people at Adobe made BalanceText to solve this exact problem, which works perfectly for my use case.

I also need to account for very long titles. When that happens, the title will max out at 3 lines, and an ellipsis will be added if necessary.

Long blog post title with an ellipsis

Development

After looking at a few options, gatsby-plugin-printer is a perfect fit for how I want to set it up. It’s worth noting that the only version of the plugin that works for me is 1.0.8.

Installation

Install with npm:

npm install --save gatsby-plugin-printer@1.0.8

Install with yarn:

yarn add gatsby-plugin-printer@1.0.8

Usage

After installing, add gatsby-plugin-printer to your gatsby-config.js file.

module.exports = {
  plugins: [`gatsby-plugin-printer`],
}

The Gatsby plugin works by taking a screenshot of a component with our data, so we need to create a component to handle that.

import React from "react"

export default () => {
  return (
    <div
      className="wrapper"
      style={{
        width: 2400,
        height: 1260,
        backgroundColor: "black",
        color: "white",
      }}
    >
      <h1>Title</h1>
    </div>
  )
}

The size of the wrapper <div></div> will be the size of the screenshot, and so I just had to put in 2400 and 1260, respectively.

Then we need a way to feed our title into the printer component. We can do that from our gatsby-node.js file.

exports.onCreateNode = ({ node }) => {
  // Get the current path of the blog file and return the slug and the content type
  let filePathSplit = node.fileAbsolutePath.split("/")
  let contentType = filePathSplit[filePathSplit - 3] // "~/content/blog/og-image/index.tsx" --> "blog"
  let fileName = filePathSplit[filePathSplit.length - 2] // "~/content/blog/og-image/index.tsx" --> "og-image"

  // Check if the content type is a blog post
  if (node.internal.type === "Mdx" && contentType === "blog") {
    // Start the "printing" job to generate the OG Image
    createPrinterNode({
      id: node.id,
      fileName: fileName, // the file name of the generated image, which is set to match my blog post slug
      outputDir: "images/blog/", // the location as to where the OG image will be stored, relative to the /public/ folder
      data: {
        // Any data that needs to be fed into the printer component
        title: node.frontmatter.title,
      },
      component: require.resolve("./src/components/seo/BlogThumbnail.js"), // The printer component
    })
  }
}

Now we pass the data into the printer component.

import React from "react"

export default ({ title }) => {
  return (
    <div
      className="wrapper"
      style={{
        width: 2400,
        height: 1260,
        backgroundColor: "black",
        color: "white",
      }}
    >
      <h1>{title}</h1>
    </div>
  )
}

BalanceText

Installation

To install BalanceText, I just need to install the React plugin.

Install with npm:

npm install react-balance-text

Install with yarn:

yarn add react-balance-text

Usage

Then I can wrap the text element I needed to be balanced with <BalanceText></BalanceText> and add additional styling.

import React from "react"
import BalanceText from "react-balance-text"

export default ({ title }) => {
  const styles = `
    @font-face {
      // Setup the font here
    }

    * {
      font-family: Unica77;
    }
  `

  return (
    <div
      className="wrapper"
      style={{
        width: 2400,
        height: 1260,
        padding: 160,
        display: "flex",
        flexDirection: "column",
        justifyContent: "space-between",
        boxSizing: "border-box",
        overflowX: "hidden",
        backgroundColor: "black",
        color: "white",
      }}
    >
      <style type="text/css" scoped>
        {styles}
      </style>
      <svg width="134" height="123" xmlns="http://www.w3.org/2000/svg">
        // Logo image
      </svg>
      <BalanceText
        style={{
          fontWeight: 500,
          fontSize: "11rem",
          lineHeight: 1.2,
          margin: 0,
          width: "100%",
        }}
      >
        {title}
      </BalanceText>
    </div>
  )
}

Further reading