When links are shared on social media, most platforms will use the og:
meta tags like og:image
, to generate a pretty preview.
Especially when you're writing blogposts, creating these images by hand for every single post is very time consuming. I will show you how I dynamically generate these images on the fly for my own blog.
If you want to know how your pages look when shared, you can use the og-debugger tool from meta: https://developers.facebook.com/tools/debug
You can check out my final solution on https://github.com/kitsunekyo/mostviertel.og-image
I'm pretty lazy, so I dont want to manually run some image generation script. Actually I dont want to think about og images at all. All I want to do is add a meta tag in my blog posts once, and with some tinkering it magically shows up at the desired url.
The path in our meta tag will contain an api call with all of data we need to generate the image. We can additionally programmatically generate the path, so out blog page layout takes care of generating the path for us.
This is a perfect usecase for a serverless function, since its a nicely encapsuled piece of logic that can run independently of my blog. For simplicity I'm going to host it on vercel. This allows me to use typescript without any build step and deploy it free of hassle (and cost).
prerequesites
make sure you're on node v14 as headless chromium does not work with node v16 on vercel
Lets setup our source code first
setup the serverless function
The next step is pretty simple. We need an api endpoint that returns the desired contents as a html page. This allows us to use basic html and css to design our layout. For serverless functions all you need is a handler
function.
To debug this locally with ease you can use the @vercel/cli
package. Install it with npm i -g @vercel/cli
You can then run vercel dev
which will walk you through the setup and allow you to run your serverless function on localhost.
When you browse to http://localhost:3000/api?title=hello+world you should now get your html page returned from the server.
generating a screenshot
For og:images we need image files, so what we can do is generate a screenshot from our created page and serve this image instead of the html. There's two well known packages for this: puppeteer and playwright. Both of these packages allow you to open a headless browser, and do some stuff like taking screenshots with it. I initially used puppeteer but it was so slow that I ran into timeout issues when deploying on vercel. So we're gonna use playwright instead.
Usually you would install playwright
directly, which downloads executables for common browsers for you. Since serverless functions are space limited, we don't want that. Thankfully vercel / and aws (which is what vercel use) have a package to interact with a preinstalled chromium instance.
Install it with npm i chrome-aws-lambda
. Instead of installing playwright
run npm i playwright-core
, which is just a small package to interact with chromium, without the executables themselves.
In our local development we dont have chrome-aws-lambda
available though, so we need some conditional to use our pre-installed chrome from our machine.
Create a api/chromium.ts
file and add the following contents:
getPage()
is our function where a new browser window will be opened.
To save some computation we dont always generate the page from scratch for every api call. By adding the global
_page
variable in the module, our serverless function instance will only create it once and reuse it on consecutive calls.
getScreenshot()
is what we will call on our api endpoint. It generates a page, sets the viewport size to our desired aspect ratio (1200px x 630px is the recommended size for og images) and we will inject our html into this new page. This means that the browser will render our html, like it did when we browsed to it. Only now we can call page.screenshot
. This will give us the screenshot as blob, which we can send as a response in our endpoint.
Lets change our handler
so it uses this new function instead of returning the html
Browse to the url again. You should now get an image instead html as response.
As a last step we can add cache-control headers. We dont need to generate new screenshots for calls we have already made. By adding the headers, our serverless function can instantly respond with the previously generated image, should multiple calls to the same url happen.
deployment
You can use the cli command vercel
directly to deploy to vercel. Or you can create a new project in the web ui of vercel, connect it with your github repo so you get automatic deployments on push to main
.
Important here is that you set node to version 14 in the settings. chrome-aws-lambda
does not work with node version 16 yet.
integration to my blog layout
For my blog, i have the option to use custom og images when I define my blogposts in markdown. Should no custom image be set, I want to default to using the og image service we just created. So in my blog layout I have this function
I then use it in my layout like this
So whenever someone shares my blog post, it will try to resolve the ogImageUrl
for the meta tag. This will generate the image and cache it for 365 days. Now my links look pretty when shared. ✌