(WIP) Automatically Generate Branded Open Graph (OG) Images for Your Blog Posts

Jul 5
·
views
·
likes
Heads up! This is an incomplete early draft

I publish drafts in case someone will find them useful. You can find the final code towards the end but some explanations are missing.

An Open Graph (OG) image is the image social networks (such as Twitter and Facebook) pull from your website to create a preview when someone shares a link to your website.

Regardless of the effectiveness of a post title or the quality of the actual post content, an OG image is usually the first thing people notice about a shared post as they browse their feed.

In theory, a better OG image should increase how often people click through to your website and re-share the original shared post.

Problem

Solution

Use Cloudinary's generous free tier to automatically generate a branded OG image for each post.

Below is an example of the generated OG image and how it would look once shared on a social network:

In this post we will discuss how to create this example, but if you prefer you can jump straight to the final code.

What is Cloudinary?

Cloudinary is primarily an image hosting service and CDN. On top of their incredible CDN they also provide a powerful REST image manipulation and generation API. Think of it as a simplified URL-based Figma or Photoshop: you provide arguments as URL paths and it generates, caches, and returns an image file.

For example, the example we started with is generated with the following URL:

Generated image

URL to generate image. It may seem a bit complex at first but we will break it down below.

Step 1: Canvas

Upload an image to your Cloudinary account that will be used as the base layer for the generated image.

Create a utility function that returns a URL string of the composed transformations. For the sake of readability, we use an array of transformations that we join() before returning.

Background base layer
const createOgImage = () => {
  return (
    [
      // prefix: <domain/account/file_type/source_type>
      `https://res.cloudinary.com/delba/image/upload`,
      // transform composed image: width, height, quality
      `w_1600,h_836,q_100`,
      // -------------------------
      // WE WILL PLACE LAYERS HERE
      // -------------------------
      // background image: <cloudinary_public_id>
      `grain.png`,
    ]
      // join parameters with slash to form a valid URL
      // [a, b, c] => "a/b/c"
      .join("/")
  )
}

Step 2: Text

Cloudinary allows you to place text and image layers that can be individually transformed and positioned on top of a base image. For example, we can render "Hello" in Arial with a font size of 100 on top of our grain.png image by adding l_text:Arial_100:Hello to the URL.

const createOgImage = () => {
  return [
    `https://res.cloudinary.com/delba/image/upload`,
    `w_1600,h_836,q_100`,
    `l_text:Arial_100:${e(
      "Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
    )}`,
    `grain.png`,
  ].join("/")
}

Oh dear, Not exactly what we're looking for! The text is overlapping the background image. By default, if a layer (image or text) has a larger width or height than the base image, the delivered image canvas is resized to display the entire layer.

Text wrapping

Text escaping

export const e = (str: string) => encodeURIComponent(encodeURIComponent(str))

Branding

Positioning

Profile Image

Notes

Final code

Select a file
lib/createOgImage.ts
export const createOgImage = ({
  title,
  meta,
}: {
  title: string
  meta: string
}) =>
  [
    // ACCOUNT PREFIX
    `https://res.cloudinary.com/delba/image/upload`,
    // Composed Image Transformations
    `w_1600,h_836,q_100`,
 
    // TITLE
    // Karla google font in light rose
    `l_text:Karla_72_bold:${e(title)},co_rgb:ffe4e6,c_fit,w_1400,h_240`,
    // Positioning
    `fl_layer_apply,g_south_west,x_100,y_180`,
 
    // META
    // Karla, but smaller
    `l_text:Karla_48:${e(meta)},co_rgb:ffe4e680,c_fit,w_1400`,
    // Positioning
    `fl_layer_apply,g_south_west,x_100,y_100`,
 
    // PROFILE IMAGE
    // dynamically fetched from my twitter profile
    `l_twitter_name:delba_oliveira`,
    // Transformations
    `c_thumb,g_face,r_max,w_380,h_380,q_100`,
    // Positioning
    `fl_layer_apply,w_140,g_north_west,x_100,y_100`,
 
    // BG
    `grain-gradient.png`,
 
].join("/")
 
// double escape for commas and slashes
const e = (str: string) => encodeURIComponent(encodeURIComponent(str))
 

We can tell crawlers what image to use by adding the <meta property="og:image" /> meta tag in the HTML of our pages.

Series
Build a Developer Blog with Next.js

  • Planned: A Fun and Productive Techstack to Build a Developer Blog
  • Turn Freeform MDX Content into Structured Data with Contentlayer
  • Planned: Add Interactive React Components to Static Markdown Content
  • Planned: Syntax Highlighting, the Easy and Performant Way, with Shiki and Rehype Pretty Code
  • Planned: Simple Post Metrics with useSWR and Prisma
  • (WIP) Automatically Generate Branded Open Graph (OG) Images for Your Blog Posts
  • Planned: Light and Dark Mode with Next Themes