Generate Open Graph images with Next.js and TypeScript on Vercel

Hey guys ๐Ÿ‘‹๐Ÿ‘‹๐Ÿ‘‹

In this article, I will write how to generate dynamic Open Graph images with Next.js and TypeScript on Vercel.

For example, the OGI of https://ogp-sample.vercel.app/hoge is like below. (Sorry that I developed this app in Japanese, but you will see the OGI has this site's path name: 'hoge'.)

If you change the path to 'fuga' like https://ogp-sample.vercel.app/fuga, the OGI is like below.

You will see the OGI is automatically generated by the path name of URL.
Now, let's try to generate dynamic OGI!

โ€ป This article is the sixth week of trying to write at least one article every week.

Past articles are listed below.

Setup

First, let's create a template for Next.js + TypeScript.

npx create-next-app --ts

Then, import canvas because we will use canvas to generate the image for OGI.

โ€ป Because it seems that [email protected] or later does not support node14, here I use version 2.6.1. (If you need, read this comment.)

The structure should be as follows.

fonts/
pages/
ใ€€โ”œ api/
ใ€€โ”‚ใ€€โ”” ogi.ts
ใ€€โ”œ [id]/
ใ€€โ”‚ใ€€โ”” index.tsx
ใ€€โ”” index.tsx

Generate the image for OGI

The first step is to create the image for OGI.
I've heard that the best size for OGI is 1200*630, then set the size 1200*630.

(There is a high possibility of garbled Japanese characters and I loaded NotoSansJP in the fonts folder, but if you use only English, you don't need to load the font. It's depends.)

// api/ogi.ts

const createOgi = async (
  req: NextApiRequest,
  res: NextApiResponse
): Promise<void> => {
  const { id } = req.query;
  const WIDTH = 1200 as const;
  const HEIGHT = 630 as const;
  const canvas = createCanvas(WIDTH, HEIGHT);
  const ctx = canvas.getContext("2d");
  registerFont(path.resolve("./fonts/NotoSansJP-Regular.otf"), {
    family: "Noto",
  });
  ctx.fillStyle = "#FFF";
  ctx.fillRect(0, 0, WIDTH, HEIGHT);
  ctx.font = "60px ipagp";
  ctx.fillStyle = "#000000";
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  const text = "ๅ…ฅๅŠ›ใ—ใŸๆ–‡ๅญ—ใฏ" + String(id) + "ใชใฎใญใ‚“";
  ctx.fillText(text, WIDTH / 2, HEIGHT / 2);
  const buffer = canvas.toBuffer();
  res.writeHead(200, {
    "Content-Type": "image/png",
    "Content-Length": buffer.length,
  });
  res.end(buffer, "binary");
}

export default createOgi;

yarn dev and look at http://localhost:3000/api/ogi?id=hoge. If the image is created like below, succeeded.

Rewrite meta

Now let's rewrite the meta information.

With Next.js, we can use <Head> tags easily.
Since this is a dynamic OGI, we'll use SSR to handle the id and pass it as props.

// pages/[id]/index.tsx
import React from "react";
import { GetServerSidePropsContext, GetServerSidePropsResult } from "next";
import Head from "next/head";

type Props = {
  id: string;
};

export const getServerSideProps = async (
  context: GetServerSidePropsContext
): Promise<GetServerSidePropsResult<Props>> => {
  if (typeof context.params?.id === "string") {
    return {
      props: {
        id: context.params?.id,
      },
    };
  } else {
    return {
      notFound: true,
    };
  }
};

const Page = ({ id }: Props) => {
  const baseUrl = process.env.NEXT_PUBLIC_BASE_URL ?? "";
  return (
    <>
      <Head>
        <meta
          property="og:image"
          key="ogImage"
          content={`${baseUrl}/api/ogi?id=${id}`}
        />
        <meta
          name="twitter:card"
          key="twitterCard"
          content="summary_large_image"
        />
        <meta
          name="twitter:image"
          key="twitterImage"
          content={`${baseUrl}/api/ogi?id=${id}`}
        />
      </Head>
      <div>
        <h1>ๅ…ฅๅŠ›ใ—ใŸๆ–‡ๅญ—: {id}</h1>
      </div>
    </>
  );
};
export default Page;

When you visit http://localhost:3000/hoge, the image from http://localhost:3000/api/ogi?id=hoge will be placed as the OGI.

Deploy on Vercel

Now we can just deploy it on Vercel, but if deploy it normally, canvas does not work.
As written in the issue, it seems that we need to yum install when deploy.

So, change the build command.

// package.json
...
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "now-build": "yum install libuuid-devel libmount-devel && cp /lib64/{libuuid,libmount,libblkid}.so.1 node_modules/canvas/build/Release/ && yarn build",
    "start": "next start",
    "deploy": "now"
  },
...

In vercel, if you write "now-build" and specify "deploy": "now", it will build referring to the now-build command instead of default build command.

So, now the build will be done by yarn now-build instead of yarn build.

Also, don't forget to set the URL as NEXT_PUBLIC_BASE_URL since we have set NEXT_PUBLIC_BASE_URL as an environment variable.

By the way, here's the one I deployed this time โ†’ https://ogp-sample.vercel.app/
If necessary, you can check it with the share debugger or post it somewhere.

You should now have a dynamic OGI like this.

References

๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–๐ŸŒ–

Thank you for reading!
I would be really grad if you give me any feedback!

๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ

Please send me a message if you need.

๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ๐ŸŽ

77