logokimromi/Notes

@vercel/og を試す

Next.js v13 のリリースと一緒に og 作成できるライブラリが発表されているので試してみる

インストール

npm i @vercel/og

サンプルコード

pages/api/og.tsx

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

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

export default function () {
  return new ImageResponse(
    (
      <div
        style={{
          fontSize: 128,
          background: 'white',
          width: '100%',
          height: '100%',
          display: 'flex',
          textAlign: 'center',
          alignItems: 'center',
          justifyContent: 'center',
        }}
      >
        Hello world!
      </div>
    ),
    {
      width: 1200,
      height: 600,
    },
  );
}

こんな見た目の画像ができた。 image

Dynamic title

https://vercel.com/docs/concepts/functions/edge-functions/og-image-examples#dynamic-text-generated-as-image

クエリストリングで文字列を渡して動的に表示する内容を変更できる。

import { ImageResponse } from '@vercel/og';
import { NextRequest } from 'next/server';

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

export default function handler(req: NextRequest) {
  try {
    const { searchParams } = new URL(req.url);

    const title = searchParams.has('title')
      ? searchParams.get('title')?.slice(0, 100)
      : 'Default';

    return new ImageResponse(
      (
        <div
          style={{
            fontSize: 50,
            background: '#191919',
            color: '#f4f4f5',
            width: '100%',
            height: '100%',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
          }}
        >
          {title}
        </div>
      ),
      {
        width: 1200,
        height: 600,
      }
    );
  } catch (e: any) {
    console.log(`${e.message}`);
    return new Response(`Failed to generate the image`, {
      status: 500,
    });
  }
}

http://localhost:3000/api/og?title=kimromi

image

Custom font

https://vercel.com/docs/concepts/functions/edge-functions/og-image-examples#using-a-custom-font

Google Fonts とかからダウンロードしたフォントを利用できる。

import { ImageResponse } from '@vercel/og';
import { NextRequest } from 'next/server';

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

const font = fetch(
  new URL('../../assets/NotoSansJP-Medium.otf', import.meta.url)
).then((res) => res.arrayBuffer());

export default async function handler(req: NextRequest) {
  try {
    const { searchParams } = new URL(req.url);
    const fontData = await font;

    const title = searchParams.has('title')
      ? searchParams.get('title')?.slice(0, 100)
      : 'kimromi';

    return new ImageResponse(
      (
        <div
          style={{
            background: '#191919',
            color: '#f4f4f5',
            display: 'flex',
            width: '100%',
            height: '100%',
          }}
        >
          <div
            style={{
              width: '100%',
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'flex-start',
              padding: '100px 150px 0 150px',
              wordBreak: 'break-all',
              fontSize: 60,
              fontFamily: '"NotoSansJP"',
            }}
          >
            {title}
          </div>

          <div
            style={{
              position: 'absolute',
              right: 120,
              bottom: 80,
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'flex-end',
              fontSize: 32,
              fontFamily: 'serif',
            }}
          >
            <img
              width="32"
              height="32"
              src="https://kimromi.com/logo.png"
              style={{
                marginRight: '0.75rem',
              }}
            />
            kimromi
          </div>
        </div>
      ),
      {
        width: 1200,
        height: 630,
        fonts: [
          {
            name: 'NotoSansJP',
            data: fontData,
            style: 'normal',
          },
        ],
      }
    );
  } catch (e: any) {
    console.log(`${e.message}`);
    return new Response(`Failed to generate the image`, {
      status: 500,
    });
  }
}

http://localhost:3000/api/og?title=ポートフォリオサイトを作った

image

フォントファイルのサイズを小さくする

image

Edge Function のサイズ制限が 1.02MB なのでフォントファイルが大きいとデプロイに失敗した。

以下のサイトを参考にサブセット化し必要な文字列だけ含んだフォントファイルにして 4.6MB → 415KB にした。

https://github.com/kimromi/kimromi.com/pull/40 で試してみて kimromi.com に導入完了。

Based on https://github.com/kimromi/notes/issues/46