Introduction

Let me start of with a question: which link are you more likely to click on, the left or the right?

Example of a twitter post with and without social media preview image

Obviously, adding a social media preview image is a great way to improve the professional look of your website and increase your click through rate (though I have no data on this). These social media images are specified using the Open Graph Protocol, which is a spec introduced by Facebook and now supported by any software that previews URLs for you. The OG image is specified in the document's head:

<head>
<meta property="og:image" content="https://url-to-your-image.png" />
</head>

This can be hooked up to a CMS so editors can choose a picture which suits their landing page, blog post, news article… the most. It is important that the preview gives the user a clear idea of what to expect on the webpage, similar to a thumbnail on a YouTube video.

But what if the page's content is not visual at all, and no photo suits the page's contents? And what if we have a lot of these pages with dynamically generated content? Luckily we are not limited to photography. We can use text! The prime example of this is how GitHub generates previews for issue URLs: they create an image containing enough info such that users know what they will click on. These images are generated on-demand, as storing a picture for every GitHub pull request or issue would be unfeasible. Another example is Vercel, which even open-sourced their Open Graph Image as a Service that they occasionally use on blog posts or announcements.

GitHub issue OG image

In this article we will find out how we can create these dynamic Open Graph images in Next.js. We will use our In The Pocket Tech Radar as an example. Our Tech Radar reflects our current view on software engineering technologies. It includes the technology choices we’ve made and motivates why we’ve made them. It also shows which technologies are “on our radar”, and why we consider adopting them.
Each technology has its own webpage, and we have more than 100 technologies on our radar. Manually creating an OG image for each technology would be a boring, repeating task.

TL;DR

Don't care about the details? Here's a small tutorial on how to add an API route to your Next.js app that generates the images on demand.

First, install next-api-og-image.

yarn add next-api-og-image

Then configure an API route

// pages/api/og-image.ts
import { withOGImage } from 'next-api-og-image';

interface QueryParams {
stage: string;
name: string;
}

export default withOGImage<'query', QueryParams>({
template: {
// include HTML template here
html: ({ name, stage }) => `<h1>${name} - ${stage}</h1>`,
},
cacheControl: 'public, max-age=604800, immutable',
dev: {
inspectHtml: false,
},
});

And call the route from the web page:

// pages/technology/[slug].ts
<Head>
<title>Next.js | Tech Radar</title>
<meta
property="og:image"
content="https://inthepocket.tech/api/og-image?name=Next.js&stage=adopt"
/>

</Head>

and done!

Behind the scenes

As you see in the code above, we are using the next-api-og-image library with almost no configuration required to generate the images. Super easy! The interesting part however is what the library does under the hood.

The withOGImage method exposes a GET /api/og-image?name=Next.js&stage=adopt API route with some query parameters we can define ourselves. In the case of our Tech Radar, we use the name and stage parameters. The API route returns an image containing the name and stage we provided:

GitHub issue OG image

When requesting the API route, the Next.js serverless function will actually spin up a web browser on the server (a headless instance of Chromium, using chrome-aws-lambda). Next, a webpage will be generated with HTML we can define ourselves. This HTML will be used to construct the image. That means that as a developer we can generate images using HTML and CSS, technologies we are already familiar with!

  template: {
html: ({ name, stage }) => `<h1>${name} - ${stage}</h1>`,
},

Of course we are not limited to a simple h1 tag, this can be a full HTML document with styles, images and custom fonts.

Finally, a screenshot of the webpage is taken using puppeteer and sent to the client-side.

We add some aggressive caching (public, max-age=604800, immutable) to prevent generating the same image too many times, because the image generation is quite slow and computationally intensive. The way we set up caching is also the reason we pass the query parameters and not just the technology's ID. You might think it is cleaner to fetch the technology's stage and name from the CMS inside the API route, but this makes caching more tricky. By adding all variable elements as query parameters, one URL will always return the same image and we can use the immutable setting on the caching. If the stage or the name of a technology would change on the Tech Radar, we will update the URL, and not update the image behind the URL.

Conclusion

Adding dynamic Open Graph images in Next.js is very low effort, but makes your site feel 10x more professional!