When I created this blog I wanted 0 friction to writing posts, but I still wanted them to use all the latest and greatest SEO magic because views = validation right?
Next.js OG image generation
This blog (and all my blogs) use Next.js. It has its quirks but I use it so often that changing to something like Astro would have likely led to another unfinished project. Using Next.js makes dynamically generating OG images super easy.
I love a good OG image, and I think they're vital when setting up a blog/website. If your site doesn't have an OG image, I'm probably not clicking on that link you just posted on Twitter. By dynamically generating it, I don't have to spent hours finding the "right" image. I can just git push my mdx, deploy the site and know that the reason no one looks at my blog is not the OG image.
The code
I've added a route at app/og/route.tsx, and in there I've defined the following (I'm using open-next, with SST so runtime args may be different for you):
Then to each blog I add the following:
If there is an image for the post, that will render otherwise we dynamically generate the OG image using the article title.
ImageResponse
Now most of this just follows the ImageReponse docs that can be found in the Next.js docs on Dynamic Image Generation. ImageResponse accepts an element and an options object. The element is just a React component with some manual styling applied, with some caveats on what styles can be used outlined in the Next.js docs above (e.g. no css grid). The options allows us to define the size, caching and custom fonts.
Custom Fonts
This was a pain in the arse to set up. Figuring out where I needed to put my font file took longer than it probably should. There are no google fonts here, so we have to download the font we want to use and fetch it in the following block. The font should be placed in the public folder of your Next.js project:
and then we append it to the options object:
Custom background image
Similarly to the custom font, we need to place our image in the public directory, then access it using our site url. I'm pretty sure this has to be a jpg/png, none of that WebP stuff here.
We can then use this in our component, with the absolute site url: