profile image

Next.jsブログでOGP画像を動的生成して返すAPI Routesを実装してみた

Next.jsReact

このブログの記事をSNSで共有するときに、OGP画像を設定してオシャレに見せたい欲が出てきました。
ただ記事1つ1つにOGP画像を設定するなんて面倒なことはしたく無いので、ZennやQiitaのように記事のタイトルを使ってOGP画像を動的に生成することにしました。
Next.jsにはAWSのLambdaを使ってエンドポイントを実装するAPI Routesという機能があり、その機能を使ってOGP画像を返すエンドポイントを作成するnext-api-og-imageというライブラリがあったのでそちらを使って実装してみました。

https://github.com/neg4n/next-api-og-imagehttps://github.com/neg4n/next-api-og-image

こちらのライブラリのREADME通りに導入し、下記のファイルを書いて実装しました。

pages/api/ogp.tsx
import { withOGImage } from 'next-api-og-image'
import { getPostSlugs, getPostBySlug } from '../../lib/api'

enum QueryParams {
  'slug',
}

export default withOGImage<'query', keyof typeof QueryParams>({
  strategy: 'query',
  template: {
    react: ({ slug }: { slug: string }) => {
      const allSlugs = getPostSlugs().map(s => s.replace(/\.md$/, ''))
      const isExistSlug = allSlugs.includes(slug)
      const { title } = isExistSlug ? getPostBySlug(slug, ['title']) : { title: 'Not Found' }

      // タイトルの文字のみのフォントを取得
      const style = `
        @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP&display=swap&text=${title}');
        @font-face {
          font-style:  normal;
          font-weight: normal;
        }
        body {
          font-family: 'Noto Sans JP', sans-serif;
        }
        .container {
          width: 100vw;
          height: 100vh;
          display: flex;
          flex-direction: column;
          justify-content: center;
          align-items: center;
        }
      `

      return (
        <html>
          <head>
            <style dangerouslySetInnerHTML={{ __html: style }} />
          </head>
          <body>
            <div className="container">
              <h1>{title}</h1>
            </div>
          </body>
        </html>
      )
    },
  },
  dev: {
    inspectHtml: false,
  },
})

記事のslugをapiのquery経由で渡し、一致した記事のタイトルを使った画像を生成して返すエンドポイントが実装できました。

slugが一致する記事が存在しない場合は404のレスポンスを返したかったのですが、ライブラリを使って404レスポンスを返す方法がわからなかったので、とりあえず記事タイトル名の代わりにNot Foundと書かれた画像を返すようにしました。

あとは記事のOGP画像先をhttps://owndomain/api/ogp?slug={slug}という形で指定してあげれば、下記のような形でOGP画像が設定できます。

next-api-og-imageを使うメリット

API Routesを使うだけなので、別のプロジェクトを用意する必要がなく、スピーディーに実装できました。
また、生成する画像もReactのコンポーネントを書く形でスタイリングができるのはとても良いです。

next-api-og-imageを使うデメリット

  • API Routesが動かない環境では使えなくなる。
  • API RoutesはAWSのLambdaで処理を行う関係上、関数と関連リソースを50MB以内にする必要があり、next-api-og-imageは内部でchrome-aws-lambdaを使っており、そのライブラリが49.7MB(v10.1.0の場合)で、重い日本語フォントはWeb経由で取得する方法を取らざるを得なかったため、豆腐が表示される可能性がある。
  • 画像生成が負荷が高い処理であるためレスポンスまで少し時間がかかってしまい、初回リクエストの場合は正常にOGP画像を載せられない場合がある。
    (2回目以降のリクエストの場合には結果がキャッシュされるため問題なくなる。)

現状はとりあえず使っていこうと思っていますが、デメリットもまあまああるので、より良い方法を探していくつもりです。
OGP画像のデザインもオシャレにしたい。