Generating Dynamic Open Graph Image in NextJs

A step-by-step guide to creating dynamic Open Graph images in Next.js, including custom fonts and styling.

Published on

3 min read

When adding Open Graph metadata to your web pages, it's important to include images that align with your content. In this guide, we explore how Next.js allows us to generate these images on-the-fly with the help of @vercel/og. If you are wondering the capabilities of it, check out the official og playground.

Installation and Setup

For those using Next.js, simply import { ImageResponse } from "next/og". If not, run:

npm install @vercel/og

First, create an API route in the App Router. Under /app, create /app/api/og/route.tsx

Creating a Basic GET Function

We start by setting up a basic GET function using ImageResponse:

import { ImageResponse } from "next/og";

export const runtime = "edge";

export async function GET() {
  return new ImageResponse(
    <div style={{ 
      height: "100%", 
      width: "100%", 
      display: "flex", 
      alignItems: "center", 
      justifyContent: "center", 
      backgroundColor: "#282c34", 
      color: "#fff", 
      fontSize: 48 
    }}>
      My Image
    </div>,
    { width: 1200, height: 630 }
  );
}

Adding Dynamic Title and Image

To make our image more relevant, we can pass a title and image URL as query parameters.

import { ImageResponse } from "next/og";

export const runtime = "edge";

export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const title = searchParams.get("title")?.slice(0, 150) || "Default Title";
  const imageUrl = searchParams.get("image");
  return new ImageResponse(
      <div style={{ 
        height: "100%", 
        width: "100%", 
        display: "flex", 
        alignItems: "center", 
        justifyContent: "center", 
        gap: 24,
        backgroundColor: "#282c34", 
        color: "#fff", 
        fontSize: 48,
        fontFamily: "Inter",
      }}>
        {imageUrl && <img src={imageUrl} style={{ width: 400, height: 400, borderRadius: 16 }} />}
        <b>{title}</b>
      </div>,
    { width: 1200, height: 630 }
  );
}

There you have it!

Play around with it to match your theme. Of course, there are more things you might wanna add or change. One that comes in to mind is the font, so let's also see how can we import fonts to the image generation.

Using Custom Fonts

For now, Next.js provides the most basic font to use. So we need to import a custom one.

Importing custom fonts might be very tricky, make sure you follow all the steps.

1. Download a Font

First, find a font that you want and download it. Nextjs suggest it to be .tff, but .woff should work just fine. I will be using InterDisplay-ExtraBold.ttf from https://rsms.me/inter/.

Then, add the font file to your project directory, e.g., /assets/InterDisplay-ExtraBold.ttf.

The structure should look like this:

-- /assets
---- /InterDisplay-ExtraBold.ttf
-- /app

2. Enable Edge Runtime

Ensure your API route uses the "edge" runtime.

import { ImageResponse } from "next/og";

export const runtime = "edge"; // Must Add

export async function GET(request: Request) {
...

3. Fetch and Apply the Font

const fontData = await fetch(new URL("../../../assets/InterDisplay-ExtraBold.ttf", import.meta.url)).then(res => res.arrayBuffer());

return new ImageResponse(
  <div style={{ ...styles, fontFamily: "Inter" }}>...</div>,
  {
    width: 1200,
    height: 630,
    fonts: [{ name: "Inter", data: fontData, style: "normal" }]
  }
);

The complele code with custom font

import { ImageResponse } from "next/og";

export const runtime = "edge";

export async function GET(request: Request) {
  try {
    const { searchParams } = new URL(request.url);
    const title = searchParams.get("title")?.slice(0, 150) || "Default Title";
    const imageUrl = searchParams.get("image");
    const fontData = await fetch(
      new URL("../../../assets/InterDisplay-ExtraBold.ttf", import.meta.url)
    ).then(res => res.arrayBuffer());
    
    return new ImageResponse(
      <div style={{ 
        height: "100%", 
        width: "100%", 
        display: "flex", 
        alignItems: "center", 
        justifyContent: "center", 
        gap: 24,
        backgroundColor: "#282c34", 
        color: "#fff", 
        fontSize: 48,
        fontFamily: "Inter",
      }}>
        {imageUrl && <img src={imageUrl} style={{ width: 400, height: 400, borderRadius: 16 }} />}
        <b>{title}</b>
      </div>,
      {
        width: 1200,
        height: 630,
        fonts: [{ name: "Inter", data: fontData, style: "normal" }]
      }
    );
  } catch (e: any) {
    console.log(`${e.message}`);
    return new Response(`Failed to generate the image`, {
      status: 500,
    });
  }
}