Generating Open Graph images with Gatsby

Outdated: This article is outdated. I have since moved my blog to Next.js but everything here should still work for Gatsby 2.0.

Since reach is such an important aspect of 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 preview for all my blog posts.

Current Open Graph preview for all 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 preview is that I want the tappable area to be larger than an ordinary link.

Small Twitter card design for external links
Small Twitter card design
Large Twitter card design for external links
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 information shown on Twitter and Facebook card designs

The data I plan on repeating in the preview 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 preview 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 preview 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 preview is that sometimes, the text would flow one or two words onto a second line, which looks really bad.

Widowed title in an Open Graph preview
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 be 3 lines at most, and an ellipsis will be added.

Long blog title with 3 lines and added ellipsis in an Open Graph preview
Long blog post title with an ellipsis

Code

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:

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

Install with yarn:

0yarn add gatsby-plugin-printer@1.0.8

Usage

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

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

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

0import React from "react"
1
2export default () => {
3  return (
4    <div
5      className="wrapper"
6      style={{
7        width: 2400,
8        height: 1260,
9        backgroundColor: "black",
10        color: "white",
11      }}
12    >
13      <h1>Title</h1>
14    </div>
15  )
16}

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.

0exports.onCreateNode = ({ node }) => {
1  // Get the current path of the blog file and return the slug and the content type
2  let filePathSplit = node.fileAbsolutePath.split("/")
3  let contentType = filePathSplit[filePathSplit - 3] // "~/content/blog/og-preview/index.tsx" --> "blog"
4  let fileName = filePathSplit[filePathSplit.length - 2] // "~/content/blog/og-preview/index.tsx" --> "og-preview"
5
6  // Check if the content type is a blog post
7  if (node.internal.type === "Mdx" && contentType === "blog") {
8    // Start the "printing" job to generate the OG Image
9    createPrinterNode({
10      id: node.id,
11      fileName: fileName, // the file name of the generated preview, which is set to match my blog post slug
12      outputDir: "images/blog/", // the location as to where the OG preview will be stored, relative to the /public/ folder
13      data: {
14        // Any data that needs to be fed into the printer component
15        title: node.frontmatter.title,
16      },
17      component: require.resolve("./src/components/seo/BlogThumbnail.js"), // The printer component
18    })
19  }
20}

Now we pass the data into the printer component.

0import React from "react"
1
2export default ({ title }) => {
3  return (
4    <div
5      className="wrapper"
6      style={{
7        width: 2400,
8        height: 1260,
9        backgroundColor: "black",
10        color: "white",
11      }}
12    >
13      <h1>{title}</h1>
14    </div>
15  )
16}

BalanceText

Installation

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

Install with npm:

0npm install react-balance-text

Install with yarn:

0yarn add react-balance-text

Usage

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

0import React from "react"
1import BalanceText from "react-balance-text"
2
3export default ({ title }) => {
4  const styles = `
5    @font-face {
6      // Setup the font here
7    }
8
9    * {
10      font-family: Inter;
11    }
12  `
13
14  return (
15    <div
16      className="wrapper"
17      style={{
18        width: 2400,
19        height: 1260,
20        padding: 160,
21        display: "flex",
22        flexDirection: "column",
23        justifyContent: "space-between",
24        boxSizing: "border-box",
25        overflowX: "hidden",
26        backgroundColor: "black",
27        color: "white",
28      }}
29    >
30      <style type="text/css" scoped>
31        {styles}
32      </style>
33      <svg width="134" height="123" xmlns="http://www.w3.org/2000/svg">
34        // Logo preview
35      </svg>
36      <BalanceText
37        style={{
38          fontWeight: 500,
39          fontSize: "11rem",
40          lineHeight: 1.2,
41          margin: 0,
42          width: "100%",
43        }}
44      >
45        {title}
46      </BalanceText>
47    </div>
48  )
49}

Further reading