Open Graph Images: Automatically Generate OG Images From Post Content

Jul 5
·
views
·
likes

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 the tweet or the quality of the actual linked content, an OG image is usually the first thing people notice about a shared post on social media.

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

The OG image we will be creating and how it could look once shared on social media.

Problem

Solution

Use Cloudinary's generous free tier and flexible API to automatically generate a branded OG image for your blog posts.

What is Cloudinary?

Cloudinary is primarily an image hosting service and 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 image on the left is generated with the following URL:

Generated image

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

Choose your journey

Follow along the step-by-step walkthrough or jump straight to the complete code at the bottom of the post.

Base Image

Background base layer
const createOgImage = () => {
  return (
    [
      // prefix: <domain/yourCloudinaryId/file_type/source_type>
      `https://res.cloudinary.com/<cloudinaryId>/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-gradient.png`,
    ]
      // join parameters with a slash to form a valid URL
      // [a, b, c] => "a/b/c"
      .join("/")
  )
}

The path we send to Cloudinary must form a valid URL. Arguments are separated by commas: w_1600,h_836,q_100. Transformation layers can be chained and are separated by slashes: w_1600,h_836,q_100/grain.png

Overlays

Cloudinary allows you to place text and image layers that can be individually transformed and positioned on top of a base image.

Overlays consist of two components the l_ layer that starts the overlay definition and includes layer transformations and an fl_layer_apply flag that closes the definition and includes placement qualifiers.

l_<public_id>/<transformations>/fl_layer_apply,<placement qualifiers>

Text layer

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

Text wrapping

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.

const createOgImage = () => {
  return [
    // ...
    `l_text:Arial_100:${e("...")},c_fit,w_1400`,
    // ...
  ].join("/")
}

Text styling

You can make your OG image fit your brand by customizing the font and color. One powerful Cloudinary feature is that you can use any typeface listed on Google fonts. In this case, I am using the same font and color as my website.

const createOgImage = () => {
  return [
    // ...
    `l_text:Karla_72_bold:${e("...")},co_rgb:ffe4e6,c_fit,w_1400`,
    // ...
  ].join("/")
}

Positioning layers

const createOgImage = () => {
  return [
    // ...
    `l_text:Karla_72_bold:${e("...")},co_rgb:ffe4e6,c_fit,w_1400,h_240`,
    `fl_layer_apply,g_south_west,x_100,y_180`,
    // ...
  ].join("/")
}

Multiple layers

You can use what we've learned to create a new text layer to house post details such as the author, publish date, and category.

const createOgImage = () => {
  return [
    // ...
    `l_text:Karla_72_bold:${e("...")},co_rgb:ffe4e6,c_fit,w_1400,h_240`,
    `fl_layer_apply,g_south_west,x_100,y_180`,
    `l_text:Karla_48:${e("...")},co_rgb:ffe4e680,c_fit,w_1400`,
    `fl_layer_apply,g_south_west,x_100,y_100`,
    // ...
  ].join("/")
}

Image layers

You can use image layers to embed other images into your composition. These images can be fetched from your Cloudinary media library, an external URL, or even a user's profile image on a social network.

Profile image

We'll use the latter to add a headshot to our OG image that dynamically syncs with a user's Twitter profile image. Changing the profile image on Twitter will eventually (after caches expire) update the headshots of all generated OG images.

const createOgImage = () => {
  return [
    // ...
    // 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`,
    // ...
  ].join("/")
}

Closing thoughts

There is a lot more you can do with Cloudinary's URL-based API. Hopefully, this is a good starting point for you to create your own unique OG images that stand out and lead to more people viewing your content.

Final code

Select a file
lib/createOgImage.ts
export const createOgImage = ({
  title,
  meta,
}: {
  title: string
  meta: string
}) =>
  [
    // ACCOUNT PREFIX
    // Add your own Cloudinary account ID.
    `https://res.cloudinary.com/cloudinaryId/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))
 
Series
Build a Developer Blog with Next.js