Benjamin Lannon

Select a theme. Click on the overlay or the button again to exit

Creating Open Graph Images with React & Gatsby

Posted on:

I had a simple goal. I wanted to add some style to the embed cards for my blogposts Twitter injects for links with their meta tags. With this, I approached it using a Gatsby plugin by Chris Biscardi to create a React component so I could create the image programmatically as part of my Gatsby build process.

gatsby-plugin-printer

Chris wrote a plugin named gatsby-plugin-printer which provides an API to generate images out of React components you build.

To start out, download the plugin and add it to your gatsby-config.js file.

yarn add gatsby-plugin-printer@1.0.8
module.exports = {
  plugins: [`gatsby-plugin-printer`],
};

You will notice I am using a specific version of said plugin as there is a newer version but Puppeteer which is used in the plugin had a major update on how they manage screenshots. Hopefully in the near future there will be an update for this plugin that fixes such, but for the time being, I am pinning to 1.0.8.

I already have a setup of MDX files being generated and managed. Now in gatsby-node.js file, a Printer node can be created using the createPrinterNode function from gatsby-plugin-printer.

exports.onCreateNode = ({ node, actions, getNode }) => {
  // create printer nodes only on MDX nodes that are blogposts
  if (
    node.internal.type === "Mdx" &&
    node.fileAbsolutePath.includes("/blog/")
  ) {
    // get the folder name (ex: /blog/2019-11-04-github-actions-changelog-workflow/index.md -> 2019-11-04-github-actions-changelog-workflow)
    let filePathSplit = node.fileAbsolutePath.split("/");
    let fileName = filePathSplit[filePathSplit.length - 2];

    createPrinterNode({
      id: node.id,
      fileName, // the filename of the image to be generated
      outputDir: "og-images/blog", // relative to the 'public' folder.
      data: {
        // The data you wish to pass down to the react component to be rendered
        title: node.frontmatter.title,
        date: moment(node.frontmatter.date).format("MMM, Do, YYYY"),
      },
      component: require.resolve("./src/printer-components/blogpost.js"), // the react component to be used.
    });

    // create a field to be then used later on for usage
    actions.createNodeField({
      node,
      name: "ogFileName",
      value: fileName,
    });
  }
};

The blogpost printer component then gets the fields in the data param as props.

import React from "react";

const topDivStyles = {
  width: 1280,
  height: 720,
  padding: 30,
  paddingBottom: 0,
  boxSizing: "border-box",
  overflowX: "hidden",
  backgroundImage: "linear-gradient(270deg, #747dbc 21%, #8CB5D9 92%)",
  color: "white",
  fontFamily: "Arial, Helvetica, sans-serif",
};

const innerDivStyles = {
  fontSize: 60,
  display: "flex",
  flexDirection: "column",
};

const h1Styles = {
  fontSize: "1.4em",
  marginBottom: 0,
};

const h2FirstStyles = { fontSize: "1em", flexGrow: 1 };

const h2SecondStyles = { fontSize: "1em", textAlign: "right", marginRight: 30 };

const BlogPostComponent = ({ date, title }) => {
  return (
    <div style={topDivStyles}>
      <div style={innerDivStyles}>
        <h1 style={h1Styles}>{title}</h1>
        <h2 style={h2FirstStyles}>{date}</h2>
        <h2 style={h2SecondStyles}>Lannonbr.com</h2>
      </div>
    </div>
  );
};

export default BlogPostComponent;

After everything is set, gatsby-plugin-printer will then generate some react components with the data that is passed down. During the build process, it will compile down those components with Rollup and Babel into a bundle that is passed into a Puppeteer browser instance. As I set the main container to be 1280x720px, it will then generate an image of that size with Puppeteer.

Finally, in my template, I can pass that ogFileName field I created in gatsby-node into the template with a GraphQL query. Once it is available, I can pass the content into a react-helmet component to tell sites like Twitter that I want an embed and where to find the images.

<Helmet
  meta={[
    {
      name: `twitter:card`,
      content: "summary_large_image",
    },
    {
      name: `twitter:title`,
      content: data.mdx.frontmatter.title + " | Benjamin Lannon",
    },
    {
      name: `twitter:image`,
      content: `https://lannonbr.com/og-images/blog/${ogFileName}.png`,
    },
    { name: `twitter:site`, content: `@lannonbr` },
  ]}
/>

Results

Once all of this is done and live, Twitter will now render the blogpost links as card embeds.

Card in Twitter

Also given Open Graph is a public standard, other platforms like Discord have also implemented this in their clients:

Card in Discord

Although this is done with Gatsby, it can be extrapolated out to use other frameworks or no framework at all as you just have to generate some HTML that will be rendered in a chrome window to take the screenshot.

For more info on what is available in OpenGraph, take a look at their main site: https://ogp.me/