If you follow me on social media, you're probably aware of the social image that shows up for any articles I share from this site. It's a giant image of my face. Now I don't know about you but I don't think this is a great image for getting engagment. I wouldn't click it...
This Tweet has failed to load via the Twitter API, this may be due to the changes made by Musk or maybe I broke something. You can still access the tweet by clicking the link button below.
This is done by setting a page's OG (Open Graph) Image in the html head of your page. The Open Graph protocol allows you to turn any web page into rich objects for social media sites. In the example above you can see that the tweet shows an image, the page title and short content extract. If you were to check the head element of this page, you'd find a few different meta properties prepended with.
og but today we're specifically looking at
og:image and its twitter substitute
twitter:image. As this page is using svelte, it was added in the following manner.
As you can see it's wrapped with a little
if statement to ensure that we only add it if there is one. In practice however, the code just uses the author image if there isn't one.
I don't actually have a way to create OG images at the moment but Github did a post not too long ago about creating a service for generating OG Images dynamically from repositories. (Which can be found here.) I took this as inspiration, and decided to build something similar using Cloud Functions. As OG Images are downloaded only a few times by each social media platform, it doesn't make sense to pay for a service to always be running.
I'll go through setup, but I'm going to assume some understanding of setting up typescript projects with Rollup.
To begin, I created a new folder in my dev environment and ran the usual commands to set up a basic node project. I installed Rollup and Typescript to set up a simple build flow before installing the
@google-cloud/functions-framework which we'll use to build our function.
Before we start writing any code, let's set up the build script to use Rollup with a standard typescript config as well as a
dev command which will run the build command and host the cloud function locally via functions framework.
Functions Framework will try and find the function named in the dev command (in this case
ogImageGen) in your
package.json's listed main file. so make sure that wherever your Rollup script is building to is listed as the main file.
With that all set up. We can start building our
src/index.ts script (or wherever your main source file is)
Building the Image Generation function
To begin, let's create a basic cloud function which returns some HTML. If you read through, you can see we import the
http function from functions framework and that this function takes a Express-style request handler as an argument.
For this, basic example we don't actually use
req yet but as you can see we call
res to inform the browser we'll be sending
UTF-8 encoded HTML. We then call
res.send to send the HTML.
If we run our
npm run dev command, you should get a localhost url to a page that matches the html variable in the code.
It may seem strange that we're starting by generating a basic html page, but in actuality we're going to use Puppeteer to generate an image from html. That means that the basics of our code are already there with the example.
Let's quickly install Puppeteer with
npm i puppeteer. After importing it into our file by adding the import statement to the top, we can start to tweak our code.
Puppeteer works by creating a headless version of chrome on the server which we can use to generate the image that we want. The first thing we need to do to achieve this is create our headless browser instance. We can do that by adding the following to the start of our ogImageGen handler.
Think of the page as a tab which we can load content into, either from a URL or from pure HTML as we're going to do here. To load our HTML into Puppeteer,We can call the
The last bit of our basic implementation is to call screenshot on our page and save the output to a buffer. We could also save it to a file system, but I'll be using Cloudflare Images for caching so I won't be needing that.
At this point we have a screenshot, but we still need to upadate our response so that we send the screenshot rather than the HTML. To do that, we change the content type to
image/png and send the buffer instead of the HTML. Once we've done that, our new function looks like this.
After running our cloud function again, we should now be sent the following image.
Now at this point, you probably want to do some html and CSS work to design your image. I recomend designing your image with static data before you go linking it to any sort of database. This is what I ended up designing quickly but you can definietly do better with a bit more time.
The recomended image size for OG Images is 1200x630. The easiest way to get a consistant image size is to create a div with an
og-image class on it. You can then set the width and height with CSS. I'd also recomend you have
box-sizing: border-box; setup. With that, you can then take a screenshot of the div instead of the whole page by updating the screenshot code to the following.
You can pass in query variables to your cloud function using
req.query which in my case, I use to do a database lookup for the correct blog post and author details. The Title, Post Date, Article Author and Avatar URL are all pulled from the database. In order to put that information into image, I created a very simple templating system using handlebars style insertion.
As I've said in previous posts, even if I don't currently have any plans to get guests in for articles. I build everything with flexibility in mind.
Deploying your function
To deploy your function, make sure you have the google cloud sdk installed on your computer and have set up a project in google cloud. From there, you should be able to use the following command to build and deploy your function.
Or if, like myself, you want to set up auto deployment, you can use the Google cloud build plugin for Github with a
cloudbuild.yaml similar to the following.
At this point you should have a working og image generator. If you can't seem to get a bit of this working, leave a comment and I'll see if I can help out. In any other case, all that's left now is for me to update meta information for the blog. Go on, see if it works and share this article on social media!
One thing that should probably be noted, memory usage by puppeteer can be upto 1gb with quite minimal sites. As such you should definietly have some form of caching in front of this project.
This site uses Webmentions to handle likes, comments and other interactions. To leave a comment, you can reply on Twitter, GitHub, or Reddit. While the site will count likes from any source, only twitter likes are currently displayed in the facepile above.