Generating Open Graph images with Vercel
Published on 26/05/2024
Last updated on 22/07/2024
Before kicking off, most of what I’ll cover in this post is already in Vercel’s documentation about Open Graph image generation, which is pretty awesome. That said, I’ll mostly walk through how I pulled it off on my website, which uses next-seo for managing SEO-related attributes in the head tag. So, if you’re also using this stack, it might add value for you! Otherwise, this is just really documenting what I’ve just learned how to do.
Installing stuff
This whole piece is based on these two packages: @vercel/og & next-seo. Ensure you install them with your preferred package manager before following the rest.
Creating the OG image
A quick primer
Open Graph images are an element part of the Open Graph protocol, which essentially is a way to make web pages more attractive when third-party applications use their URL. Do you know when you add a link to your tweet and, after you post it, an image appears? That’s the OG image, something you can optionally add to give a brand boost to your URL.
Adding the OG API route
Here’s when we’ll start to use the @vercel/og
package. It uses Vercel's infrastructure (i.e., ”Edge Functions”) to generate these images instantly. And then, the ultimate value for us is the ability to create OG images using HTML and CSS, which is a huge time-saver as opposed to managing these assets elsewhere.
For example, before giving my website this update, I created each page’s image by hand, using a combination of Figma and any hosting image site out there to generate a URL I could add to my NextSeo
component. I know... that was a terribly bad process.
Alright, to start us off, let's create a file for the OG image component in this specific route: pages/api/og.jsx
. Then, go ahead and copy this code snippet (adapted from Vercel’s docs):
1import { ImageResponse } from '@vercel/og';23export const config = {4 runtime: 'edge',5};67export default function handler(request) {8 try {9 const { searchParams } = new URL(request.url);1011 const hasTitle = searchParams.has('title');12 const title = hasTitle13 ? searchParams.get('title')?.slice(0, 100)14 : 'My default title';1516 return new ImageResponse(17 (18 <div19 style={{20 fontSize: 40,21 color: 'black',22 background: 'white',23 width: '100%',24 height: '100%',25 padding: '50px 200px',26 textAlign: 'center',27 justifyContent: 'center',28 alignItems: 'center',29 }}30 >31 {title}32 </div>33 ),34 {35 width: 1200,36 height: 630,37 },38 );39 } catch (e) {40 console.log(`${e.message}`);41 return new Response(`Failed to generate the image`, {42 status: 500,43 });44 }45}
You’ll see a few things here:
- It uses an async function, so the OG image generation doesn’t compromise any other function on your page or site.
- It uses the
edge
runtime configuration to leverage Vercel’s infrastructure, which is essentially just a way to retrieve information faster as it’s pulling from the data center closest to you. If you have a small website like mine, no need to worry about this at all. - It uses a
try...catch
statement to house theImageResponse
component, protecting the API in case any error occurs. Note that we’re already passing the title as a prop here, which is already coming in with a default value in case there’s no specific title passed via the URL. - Finally, if you visit the route
https://localhost:3000/api/og
, you should see a PNG created using the exact HTML and CSS you see there!
With this code snippet, the OG image generation is basically done. You should be able to go crazy with the design and do whatever makes sense for your site. Vercel has some cool examples set up so you learn how to take the design and DX (for example, using Tailwind instead of vanilla CSS) to the next level.
Hooking it up to next-seo
So, to then use the API created above on each page, we’ll hook this up to the NextSeo
component, exported from the next-seo package, by creating a higher-level component called NextSeoHeader
. Here’s my site’s component, for example:
1import { NextSeo } from "next-seo";23export default function NextSeoHeader({ url, title }) {4 return (5 <NextSeo6 title={`${title}-Danilo Leal`}7 canonical={`https://daniloleal.co${url}`}8 openGraph={{9 url: `https://daniloleal.co${url}`,10 title: `${title}-Danilo Leal`,11 images: [12 {13 url: `https://daniloleal.co/api/og?title=${title}`,14 alt: `${title} by Danilo Leal`,15 width: 1200,16 height: 630,17 },18 ],19 }}20 />21 );22}
I’m adding a title
and url
props to fields that the NextSeo
component exposes, making managing SEO attributes quite handy. Therefore, at the top of each page file, I add variables to avoid code repetition as they’re used by other components throughout. And then, I add them to this higher-level component like so:
1const postTitle = "Four simple accessibility improvements to apply today";2const postUrl = "/my-world/thinking/four-simple-a11y-improvements";34<NextSeoHeader title={postTitle} url={postUrl} />
Wrap up
By visiting the API route of your OG image, with your page’s title passed in as a parameter of the URL, you should be able to see whatit looks like. Here’s mine:
Overall, I know my setup is quite specific—for example, I don’t yet use any Markdown-based tool, like MDX, to write blog posts—which means this might not be super helpful, but the Vercel documentation and the whole ergonomics of using their package are pretty complete, so I hope that can help you out more, alongside with this quirky little post.