Generate Open Graph images with Next.js and TypeScript on Vercel
Tags: nextjs, typescript, vercel
Published: 2021-06-29 13:43:27

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'.)

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b4qs8bfvf8vw6ug4fc2m.png)
 
If you change the path to 'fuga' like https://ogp-sample.vercel.app/fuga, the OGI is like below.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r0fqqsszpp2sychvrt9x.png)

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.

- [Face detection with Tensorflow【React + TypeScript】](https://dev.to/yuikoito/face-detection-by-using-tensorflow-react-typescript-3dn5)
- [UI Components website Released!](https://dev.to/yuikoito/ui-components-website-released-2g8e)
- [I made 18 UI components for all developers](https://dev.to/yuikoito/i-made-18-ui-components-for-all-developers-17l6)
- [Image Transformation: Convert pictures to add styles from famous paintings](https://dev.to/yuikoito/image-transformation-convert-pictures-to-add-styles-from-famous-paintings-24ml)
- [Developed an app to transcribe and translate from images](https://dev.to/yuikoito/developed-an-app-to-transcribe-and-translate-from-images-32gp)

## 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. 

```
yarn add canvas@2.6.1
```

※ Because it seems that canvas@2.7.0 or later does not support node14, here I use version 2.6.1. (If you need, read [this comment](https://github.com/Automattic/node-canvas/issues/1779#issuecomment-819827079).)

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.)

```react
// 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.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z94wlg8kyqkd761xe5jy.png)

## 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. 

```react
// 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](https://github.com/vercel/vercel/issues/3460#issuecomment-583072848), it seems that we need to yum install when deploy.

So, change the build command.

```json

// 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.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zffuonkpexfz93jiwz1r.png)
 

## References

- https://zenn.dev/tiwu_dev/articles/68d58d4ab710af
- https://nextjs.org/docs/basic-features/data-fetching
- https://github.com/vercel/vercel/issues/3460
- https://github.com/Automattic/node-canvas/issues/1779


🌖🌖🌖🌖🌖🌖🌖🌖

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

🍎🍎🍎🍎🍎🍎

Please send me a message if you need.

yuiko.dev@gmail.com
https://twitter.com/yui_active

🍎🍎🍎🍎🍎🍎