🎉 I'm releasing 12 products in 12 months! If you love product, checkout my new blog workingoutloud.dev

Back to home

Using The New Vercel Open Graph Image Generation

On October 10th, 2022, Vercel announced their new open graph image generation feature. This feature allows you to generate images on the fly, based on the URL of the page. This is a great feature for blogs, as it allows you to generate images for each post, without having to create them manually and operates from the edge when using Vercel as the host.

As I host this blog on Vercel (at least at the time of writing), I thought it might be time to update the open graph images to display more information about the post. Whether or not I keep it this way is another story, but follow along to see how I end up implementing it.

You will need basic familiarity with Next.js as a prerequisite to understand what is going on.

Getting started

Using the documentation as a guide, I started by installing the @vercel/og package.

$ yarn add @vercel/og

At this point, the blog website was ready to start implementing the open graph API endpoint.

Creating the open graph endpoint

At this stage, we can create the open graph endpoint. Following documentation, we create a new file pages/api/og.tsx and add the following code.

// /pages/api/og.tsx import { ImageResponse } from "@vercel/og"; export const config = { runtime: "experimental-edge", }; export default function () { return new ImageResponse( ( <div style={{ fontSize: 128, background: "white", width: "100%", height: "100%", display: "flex", textAlign: "center", alignItems: "center", justifyContent: "center", }} > Hello world! </div> ), { width: 1200, height: 600, } ); }

At this point, I was able to navigate to my localhost site when running development and see the open graph that would return.

Basic open graph preview

Basic open graph preview

So far, so good. The next part was to beautify this a bit more.

Beautifying the open graph

There is out-of-the-box support for Tailwind. This makes beautifying our response a lot easier.

The other thing I want to do is grab the useful for the particular article that we are searching for.

I won't cover it here, but I wrote a small script to generate the useful post information into a JSON file that has the title as the key to so I can have O(1) access to the information.

As an example, one entry in the map looks similar to the following:

{ "2022-10-15-using-the-new-vercel-opengraph-image-generation": { "title": "Using The New Vercel Open Graph Image Generation", "description": "Following the release of Satori and Vercel's new open graph image generation, follow along as I demonstrate how I updated my own open graph images for the blog.", "tags": "beginner,nextjs,javascript", "icons": "dok,nextjs,ts", "readingTime": "2 min read", "href": "/blog/2018-07-02-hello-world", "date": "2018-07-02", "media": "https://cdn.dennisokeeffe.com/assets/2022-10-15-using-the-new-vercel-opengraph-image-generation.webp" } }

Now that the information is available for me, the next step is to create a layout that I want for my open graph images.

For this part, I went to the Vercel open graph playground and the Tailwind documentation to build out an open graph layout that I liked.

Basic design on the playground

Basic design on the playground

Afterwards, I ported over the code to my own project and made some minor changes to make it work with my own data.

import { ImageResponse } from "@vercel/og"; import dayjs from "dayjs"; import data from "../../data/_og.json"; export const config = { runtime: "experimental-edge", }; const font = fetch( new URL("../../assets/NotoSans-Regular.ttf", import.meta.url) ).then((res) => res.arrayBuffer()); const fontBold = fetch( new URL("../../assets/NotoSans-Bold.ttf", import.meta.url) ).then((res) => res.arrayBuffer()); export default async function (req) { const fontData = await font; const fontBoldData = await fontBold; const { searchParams } = new URL(req.url); const hasTitle = searchParams.has("post"); const title = hasTitle ? searchParams.get("post") : "My default title"; const post = data[title]; return new ImageResponse( ( // Modified based on https://tailwindui.com/components/marketing/sections/cta-sections <div style={{ height: "100%", width: "100%", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "space-between", backgroundColor: "#fef6e4", paddingLeft: 32, paddingRight: 32, paddingTop: 14, paddingBottom: 14, }} > <div tw="flex flex-col text-xl"> <h2 tw="text-[#001858] flex flex-col text-3xl sm:text-4xl font-bold tracking-tight text-left mb-0"> {post.title} </h2> <div tw="flex mb-0"> <div tw="flex flex-col md:flex-row w-full md:items-center justify-between"> <div tw="flex"> {post.tags.map((tag) => ( <p key={tag} tw="flex items-center justify-center rounded-full border border-transparent bg-[#f582ae] px-3 py-1 text-base font-medium text-white shadow mr-2" > {tag} </p> ))} </div> </div> </div> <p tw="m-0 text-[#172c66]">{post.description}</p> </div> <div tw="flex items-center space-between w-full"> <div tw="flex grow"> <div tw="mr-4 h-16 w-16 bg-[#8bd3dd]" /> <div tw="mr-4 h-16 w-16 bg-[#8bd3dd]" /> <div tw="h-16 w-16 bg-[#8bd3dd]" /> </div> <div tw="flex flex-col items-end justify-end grow h-full"> <p tw="text-sm m-0">{post.readingTime}</p> <p tw="text-sm font-bold m-0"> {dayjs(post.date).format("D MMM YYYY")} </p> </div> </div> </div> ), { width: 800, height: 400, fonts: [ { name: "Noto Sans", data: fontData, weight: 400, style: "normal", }, { name: "Noto Sans", data: fontBoldData, weight: 700, style: "normal", }, ], } ); }

The above braces {} are where the dynamic information will be injected.

I also added in some more functionality to get "Noto Sans" set as the font of choice, as well as adding in the ability to parse search parameters for the correct post to generate the open graph image.

At this point, the open graph image looks like this:

Ported version

Ported version

Note: I picked the color palette I liked from Happy Hues.

Adding the open graph to the blog

To add the open graph image to my posts, I would need to add the following to my blog posts:

<head> <title>Hello world</title> <meta property="og:image" content="https://blog.dennisokeeffe.com/api/og?post=2022-10-15-using-the-new-vercel-opengraph-image-generation" /> </head>

The above code fetches the blog post data from the JSON file that I created earlier when executing the code in the API route /api/og by parsing the post parameter.

Summary and the verdict

The ability to dynamically generate open graph images is powerful, but personally, I currently like the open graph images I generate statically before deploying each post.

For the time being, I will opt to keep the static open graph images, but I will keep the dynamic open graph images in mind for future projects where the capability to generate dynamic open graph images on the fly is of higher importance.

Resources and further reading

  1. Happy Hues
  2. Vercel open graph playground
  3. Tailwind
  4. Open graph image documentation
  5. Vercel blog post announcing the open graph feature

Photo credit: malinovski

Personal image

Dennis O'Keeffe

  • Melbourne, Australia

Hi, I am a professional Software Engineer. Formerly of Culture Amp, UsabilityHub, Present Company and NightGuru.
I am currently working on Visibuild.


Get fresh posts + news direct to your inbox.

No spam. We only send you relevant content.

Using The New Vercel Open Graph Image Generation


Share this post