Dynamic Open Graph Images

X @urre

You make awesome content. For that content to reach its desired effect of attracting readers or customers, it needs to be shared. A great way to display your content is to use Open Graph Protocol. Open graph tags are very useful and if you use them right, it can optimize your content for social sharing.

What are Open Graphic Meta Tags?

Open Graph meta tags allow you to control what content shows up when a webpage is shared across major social networks such as Facebook, Twitter, and LinkedIn. Aside from social media, hundreds of other content sharing tools (e.g., messenger tools such as Slack) use these tags. You can control how the title, site name, image, and descriptions are displayed. Think of it as a short intro to your content. It needs to be captivating enough for a viewer to like it or click into it.

You can put a <meta> tag in the <head> of a webpage to define this data. It looks like this:

<head>
<!-- Facebook Open Graph Image -->
<meta property="og:image" content="example.jpg"/>
<meta property="og:image:height" content="600"/>
<meta property="og:image:width" content="1200"/>

<!-- Twitter Card Image -->
<meta property="twitter:image" content="example.jpg"/>
<meta name="twitter:card" content="summary_large_image"/>
<meta name="twitter:image:alt" content="Example"/>
</head>

Note: You must use JPG or PNG for Open Graph images. SVG doesn’t work.

An example

I run a small music blog called Jazztips. All content is written in Markdown and then compiled to a static site and deployed to Netlify. The posts are then published on Twitter and Facebook automatically via IFTTT.

The start page
Single posts pages

The default card style

This is what the default sharing card looks like, with the record cover as the Open Graph image. It definitely works, but let’s make it better.

How to generate a great card image with text

It takes a long time and a lot of manual work to create unique images for every post. And different posts would require different imags and different text. Otherwise it wouldn’t stand out very much when it was shared.

Using Canvas to create an image

The HTML <canvas> element can be used to draw graphics on a web page. With a Node.js script we can read the frontmatter (artist, title etc) and then generate an image. The example below are a bit simplified.

Select file Read front matter (YML)

The Markdown frontmatter looks like this:

title: For Jimmy, Wes and Oliver
artist: Christian McBride Big Band
image:  https://res.cloudinary.com/urre/image/upload/w_600,h_600/v1606915632/screenshots/fghrcq1z1j4pk3sgyp2l.png

Below is a shortened version of the script ogimage.js

const fileFrontmatter = fs.readFileSync(`../_posts/my-example-post.md`, 'utf8')
const fileData = fm(fileFrontmatter)

The front-matter package is used here to extract all the YML data.

Create a canvas and load the image

// Now load image into the canvas and add text
loadImage(fileData.attributes.image).then((image) => {

// Just some settings
const width = 1200
const height = 630
let fontSize = 64
let lineHeight = fontSize * 1.3975
let textArtistY = 120
let textTitleY = textArtistY + 220

// Init the HTML Canvas
const canvas = createCanvas(width, height)
const context = canvas.getContext('2d')

// Fill with a light green background color
context.fillStyle = '#bdfbd5'
context.fillRect(0, 0, canvas.width, canvas.height)

// Add the image to the Canvas
context.drawImage(image, 40, 50, 600, 600)
...

Create a JPG image

Let’s do a test to see what the output is.

const buffer = canvas.toBuffer('image/jpeg')
fs.writeFileSync('./temp.jpg', buffer)

If we open up this image we can see the result.

Ok, that looks good. Now lets add the text.

Add text

Use a custom font

Since I use Spectral (designed by Production Type) I’d like to use this font to follow the design of the website.

registerFont('./spectral/Spectral-Light.ttf', {
  family: 'Spectral',
})

Draw the white background behind the text

context.fillStyle = '#fff'
context.fillRect(550, 90, 700, 500)

Add the text

// Font settings
context.font = `normal ${fontSize}pt Spectral`
context.textAlign = 'left'
context.textBaseline = 'top'

To add the text to the Canvas, the syntax is context.fillText("Our title here", 10, 50);. However, our text can vary in length and we will need to wrap it nicely to fit our canvas boundaries. I’m using a simple helper method to wrap long titles. Also, controlling the line height is not as easy as you would expect compared to CSS.

const wrapText = (context, text, x, y, maxWidth, lineHeight) => {
	var words = text.split(' ')
	var line = ''

	for (var n = 0; n < words.length; n++) {
		var testLine = line + words[n] + ' '
		var metrics = context.measureText(testLine)
		var testWidth = metrics.width
		if (testWidth > maxWidth && n > 0) {
			context.fillText(line, x, y)
			line = words[n] + ' '
			y += lineHeight
		} else {
			line = testLine
		}
	}
	context.fillText(line, x, y)
}

Now I can add Artist and Title next to the record cover:

context.fillStyle = '#000'
wrapText(context,`${fileData.attributes.artist}`,640, textArtistY, 510, lineHeight)
wrapText(context,`”${fileData.attributes.title}”`,640, textTitleY, 510, lineHeight)
Nice! Let's do some more art direction

Add the green logo circle

Just for some more art direction.

context.fillStyle = '#68d391'
context.beginPath()
context.arc(1080, 100, 50, 0, 2 * Math.PI)
context.fill()
Perfect!

Upload to Cloudinary

I store and deliver all my media assets using Cloudinary. So lets do the following:

  1. Upload the image to Cloudinary
  2. Write the HTTPS image link back to the Markdown file as frontmatter.
title: For Jimmy, Wes and Oliver
artist: Christian McBride Big Band
image:  https://res.cloudinary.com/urre/image/upload/w_600,h_600/v1606915632/screenshots/fghrcq1z1j4pk3sgyp2l.png
ogimage:  NEW IMAGE URL HERE

// Load .env file
require('dotenv').config()

// Setup Cloudinary uploader
cloudinary.config({
	cloud_name: process.env.CLOUDNAME,
	api_key: process.env.APIKEY,
	api_secret: process.env.APISECRET,
})

const newImage = cloudinary.v2.uploader.upload(
	'./temp.jpg',
	function (error, result) {
		insertLine(`../_posts/${filename.file}`)
			.content(`ogimage: ${result.secure_url}`) // Create YML variable
			.at(9) // At line 9 in my case
			.then(function (err) {
				log(`${chalk.green(`✔️ Inserted ogimage front matter`)}`)
			})
	}
)

Some npm packages used here are: chalk, insertLine and cloudinary.

Testing

If you want to see what your posts will look like on Twitter without actually tweeting it, you can test it using Twitter’s Card Validator. iframely and Facebook Debugger are also great tools I recommend.

Always check how the content looks before sharing.

Automation

There are a lot of possibilities here. You can:

  • Use it in a standalone script and go through all your posts and bulk create dynamic images.
  • Run as a script in your CI/CD pipelines, check for missing OG images in posts.
  • Use it like a CLI, passing path to a markdown file.

It’s up to you. I currently have a script that checks the latest modified post. If that doesn’t have an OG image, I create one.

Done!

Now our sharing image looks way better!

On Twitter
On Slack

I hope this post will help you make better sharing images. It is a really nice addition to help content stand out from the crowd!