Create OG Images for Your Blog with Next.js

At the same time that I released the new design for this blog using NeXT.js, Vercel announced a new library for generating OpenGraph (OG) images. You know, these images you can see when you post some links on Twitter.

Since a cover image was attached to my blog post, it seemed natural to me to use this image. But I often wonder if it was worth it to generate an image with additional content like the post title or its date. Generating an image was too complicated.

Luckily, Versailles’ OG library now makes this a lot easier. Let’s see how by implementing OG image creation for a fictional blog.

I If you are curious about Next.js and want to learn it, check out the book Serverless Web Applications with React and Next.js.

I If you like video content, I made a short video with the contents of this post:


@vercel/og, satoriand edge functions

Is it possible to generate og images @vercel/og library, self dependent satori (also made by Versailles). This package allows you to create an image from HTML and CSS code (using JSX). @vercel/og Adds a layer to make it easier to use, for example by loading a default font so you don’t have to.

On the client, such generation does not seem complicated, and we know that a . how to make it last longer using canvas Element. But on the server you had to start a headless Chrome browser, which was complicated and expensive. satori Doesn’t work that way, and can generate images efficiently on the client or in serverless works.

Even better, it can run edge workand that’s it @vercel/og does. Edge functions are a special kind of serverless functions, free of all the classic Node.js runtime features. You can only use native JavaScript features from the V8 runtime, so edge functions come with some limitations, but are very fast, and their execution is cheap.

generate an image with @vercel/og

You can follow this tutorial on any Next.js project. I will show how to start from an empty Next.js application that is generated with just the command:

$ yarn create next-app hello-og --typescript
enter fullscreen mode

exit fullscreen mode

In this project, we will use a serverless function to generate OG images, which will be accessible at the endpoint /api/og, let’s make it into a new one /pages/api/og.tsx file.

import { NextRequest, NextResponse } from 'next/server'

export const config = {
  runtime: 'experimental-edge',
}

const handle = (req: NextRequest) => {
  return NextResponse.json({ hello: true })
}

export default handle
enter fullscreen mode

exit fullscreen mode

If you’re used to serverless functions in Next.js, you might notice a few differences:

  1. exported config Thing. Here we tell Next.js that this function is a edge function,
  2. req not in parameter NextApiRequest type, but NextRequest, This is also because it is an edge function.
  3. We Return a response (instead of using a .) res object passed as parameter), of type NextResponse,

Nevertheless, if you run the application and call the endpoint, you will get the expected JSON result, as with any classic function:

$ curl http://localhost:3000/api/og
{"hello":true}
enter fullscreen mode

exit fullscreen mode

Let’s now update this function to return an image using @vercel/og, We first need to add dependency in our project yarn add @vercel/og, Then we say “Hello world!” Can generate a basic image containing the text:

import { ImageResponse } from '@vercel/og'
// ...

const handle = (req: NextRequest) => {
  return new ImageResponse(
    <div>Hello World!</div>,
    // 1200x628 seems to be the right dimensions to use for OG images.
    { width: 1200, height: 628 },
  )
}
enter fullscreen mode

exit fullscreen mode

with these three lines of code a ImageResponse, we generate an image from React code. It sounds easy, doesn’t it? You probably think “that’s great, I can use all my React components!”… well, it’s not that easy.

Even though we use JSX here, it is important to understand that it is only represented by a shorthand satori (which appears to use React, but not React-DOM). There are some constraints in the JSX code you wrote here:

  • Not all CSS properties are supported,

  • only flex Layout is supported, and every element with multiple children must explicitly use (display: flex,
  • you can’t use script either link elements, etc.

There is still much we can do by knowing these constraints. One feature I like: You can basically use the TailwindCSS classes tw props on the elements (instead of className) If you want, you can use the classic style Props for specifying CSS properties.

Let’s say we want to display a post title, an author name, and a date which we will get from the query parameter. To get these parameters, we cannot use req.query: In edge functions, we have to use req.urlthat we can parse using URL square:

const handle = (req: NextRequest) => {
  const { searchParams } = new URL(req.url)
  const title = searchParams.get('title') || 'No post title'
  const author = searchParams.get('author') || 'Anonymous'
  const date = new Date(searchParams.get('date') || '2022-11-05T12:00:00.000Z')
enter fullscreen mode

exit fullscreen mode

And then, we can use these parameters to add to the image, with some TailwindCSS classes to make everything a little more beautiful:

  return new ImageResponse(
    (
      <div tw="flex w-full h-full flex-col justify-end bg-slate-200 items-stretch">
        <div tw="flex flex-col bg-white p-8">
          <div tw="text-5xl mb-4">{title}</div>
          <div tw="text-2xl">
            {author +
              '' +
              date.toLocaleDateString('en-US', { dateStyle: 'long' })}
          </div>
        </div>
      </div>
    ),
    { width: 1200, height: 628 }
  )
}
enter fullscreen mode

exit fullscreen mode

Here’s what this first version of our OG image looks like with some parameters:

an image inside an image

That’s cool, but it misses the most important thing we all want: the cover image. let’s add one cover parameter, and call it a. use in img Element:

const handle = (req: NextRequest) => {
  const { searchParams, host, protocol } = new URL(req.url)
  // ...
  const cover = `${protocol}//${host}${
    searchParams.get('cover') || '/cover.jpg'
  }`

  return new ImageResponse(
    (
      <div tw="flex w-full h-full flex-col justify-end bg-slate-200 items-stretch">
        <img
          src={cover}
          alt=""
          tw="flex-1 w-full h-full"
          style={{ objectFit: 'cover', objectPosition: 'center' }}
        />
        {/* ... */}
      </div>
    ),
    { width: 1200, height: 628 }
  )
}
enter fullscreen mode

exit fullscreen mode

(To run the example, get an image on Unsplash and put it in public/cover.jpg,

Pay attention to how we use host And protocol Attributes from the request’s URL, because we need to use absolute URLs img Element

Another version of our OG image

This second version is much more satisfying … until we deploy it on Versailles. After deployment, if you call try to generate an image, you may get a blank image. In the Vercel log, you will see an error like this:

Error log on Vercel

It’s not clear, but the problem comes from the cover image we want to include, which looks too big. @vercel/og At the time I am writing this post. We can choose to use a smaller image, but it is very annoying…

Good news, Next.js already provides an easy way to resize images. while using next/image component, Next.js uses an endpoint /_next/image, While we can’t use component, we can use endpoint!

const cover = `${protocol}//${host}/_next/image?url=${encodeURIComponent(
  searchParams.get('cover') || '/cover.jpg'
)}&w=1200&q=75`
enter fullscreen mode

exit fullscreen mode

Add OG Image to Your Pages

Now that we know how to create our image, we can use it in the meta tag of the page header. You can find the full list of meta tags you need to define Twitter Documentation, I like to use a custom component built into many of my projects: SeoHeaders,

If you’re adding an OG image to a Next.js blog, I’m assuming you already have a component in which you know the post title, author, date, and cover image. You can create OG image url:

// It must be an absolute URL, so I use an
// environment variable for the base URL.
const imageUrl =
  `${process.env.NEXT_PUBLIC_BASE_URL}/api/og?` +
  `title=${encodeURIComponent(title)}` +
  `&author=${encodeURIComponent(author)}` +
  `&cover=${encodeURIComponent(cover)}` +
  `&date=${date}`
enter fullscreen mode

exit fullscreen mode

Then, you can pass this url SeoHeaders Component:

<SeoHeaders
  title={title}
  description=""
  author={author}
  twitterAuthor=""
  twitterSite=""
  url={process.env.NEXT_PUBLIC_BASE_URL}
  imageUrl={imageUrl}
/>
enter fullscreen mode

exit fullscreen mode

or, if you add meta Tag yourself:

<meta property="og:image" content={imageUrl} />
<meta name="twitter:image" content={imageUrl} />
enter fullscreen mode

exit fullscreen mode

Note that in order to use the image in social media, you must specify other tags: og:type, twitter:carde.t.c.


This concludes this tutorial on how to create and use OG images in a NeXT.js application! Our image is very minimalistic, but here are some ideas if you want to improve it:

  • play with opacity options (using the TailwindCSS class bg-opacity-50 For example) instead of having a white background for the text.
  • Add the avatar of the author (using the second image).
  • Add a description, or a portion of it, to the picture.
  • If your blog allows comments or likes, you can add some counters to the image.

You will find the complete source code of the example Next.js application on this GitHub repo. Don’t forget to check out Vercel’s documentation for OG image generation.

This post was first posted on my blog: Create OG Images for Your Blog with Next.js.

Cover photo by Noman Shahid.

Leave a Comment