Next.js(App Router)でOGPの画像を作る

OGP の画像をいちいち作るのが面倒だったので、Next.js の機能を使って生成できるようにしました。

OGP って何?

OGP とは、Open Graph Protocol の略で、ウェブサイトの内容をソーシャルメディアでシェアしたときに表示できるデータのことです。X や Facebook などの SNS でウェブサイトをシェアしたときに、タイトル、説明、画像などが表示されているのを見たことがありますよね。

OGP の目的は、ウェブサイトがソーシャルメディア上でシェアされた際に、適切な形式で表示されるようにすることです。URL だけではどのようなウェブサイトかわかりにくいですが、タイトルや画像などが表示できていれば、どのようなウェブサイトかわかります。

OGP を設定する

OGP は HTML の<head>内に<meta>タグとして配置されます。具体的な OGP タグには、以下のようなものがあります。

  • <meta property="og:title" content="ページのタイトル">: シェアされたページのタイトルを指定します。
  • <meta property="og:description" content="ページの説明">: シェアされたページの説明を指定します。
  • <meta property="og:image" content="画像のURL">: シェアされた際に表示される画像の URL を指定します。

この記事では、画像を Next.js の機能を使って設定していきます。

Next.js の OGP

基本は、Next.js 公式の App Router のチュートリアルを参考にしました。

OGP の設定

まずは、静的なデータを/app/layout.tsxに記述して設定します。

import { Metadata } from 'next'

const siteName = 'サイトのタイトルです'
const description = 'サイトの説明です'

export const metadata: Metadata = {
  title: {
    default: siteName,
    template: `%s - ${siteName}`,
  },
  description,
  openGraph: {
    title: siteName,
    description,
    siteName,
    locale: 'ja_JP',
    type: 'website',
    images: {
      url: `image/ogp.png`,
    },
  },
}

ページごとに異なる動的なデータは、page.tsx に記述します。

import { Metadata } from 'next'

export const generateMetadata = async (slug): Promise<Metadata> => {
  const post = await getPostBySlug(slug) // ブログの記事データの取得
  if (post) {
    return {
      title: post.title, // ページのタイトル
      openGraph: {
        title: post.title, // ページのタイトル
        description: post.description, // ページの説明
        images: {
          url: `/api/og?title=${post.title}`, // ページの画像URL
        },
      },
    }
  }
  return {}
}

これによって、HTML の<head>内の<meta>タグにデータが入ります。

<title>サイトのタイトルです</title>
<meta name="description" content="サイトの説明です" />
<meta property="og:title" content="サイトのタイトルです" />
<meta property="og:description" content="サイトの説明です" />
<meta property="og:image" content="https://~" />

これで、OGP を設定できるようになります。
画像については、API を作成して、記事のタイトルから動的に作成できるようにします。

OGP 画像の作成

画像を作成するための API を作成します。/api/og?title=記事のタイトルにアクセスしたときに、記事のタイトルの入った画像を表示できるようにします。app/api/og/og.tsxを作り、API を記述していきます。

App Router の API については、公式を参考にしました。

og:imageに入る画像を生成するためには、ImageResponse を使います。ImageResponse に JSX を渡すことで画像を描画できます。

一部省略していますが、画像を生成するソースはこんな感じです。

import { ImageResponse, NextRequest } from 'next/server' // v14.0.0以降ではnext/ogにしましょう。
import { loadGoogleFont } from '@lib/fonts'

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url)
  const title = searchParams.get('title') ?? '' // ブログ記事のタイトル
  try {
    // Google Fontの読み込み
    const googleFontArrayBuffer = await loadGoogleFont({
      family: 'Mochiy Pop One', // 好きなフォントを設定する。
      weight: 400,
      text: title,
    })
    return new ImageResponse(
      (
        <div
          style={{
            display: 'flex',
            justifyContent: 'center',
            alignItems: 'center',
            textAlign: 'center',
            width: 1200,
            height: 630,
            backgroundImage: 'url(背景画像のURL。省略)',
            backgroundSize: '1200px 630px',
            backgroundRepeat: 'no-repeat',
          }}
        >
          <div>{title}</div>
        </div>
      ),
      {
        width: 1200,
        height: 630,
        fonts: [
          {
            name: 'MochiyPopOne',
            data: googleFontArrayBuffer,
            style: 'normal',
            weight: 400,
          },
        ],
      }
    )
  } catch (error) {
    console.error('Error while generating OGP image:', error)
    return new Response(`Failed to generate the image`, {
      status: 500,
    })
  }
}

satori を使っているようですので、使える CSS にはいくつか制約があります。

フォントの読み込み

OGP の画像に Google Fonts を使います。日本語で JIS 第三、四水準漢字対応&かわいいフォントを探しました。

app/lib/font.tsを作って、GoogleFonts を読み込む処理を記述します。API のファイルに書くとビルド時にエラーが出たのでファイルを分けました。

export async function loadGoogleFont({
  family,
  weight,
  text,
}: {
  family: string
  weight?: number
  text?: string
}) {
  const params = new URLSearchParams({
    family: `${family}${weight ? `:wght@${weight}` : ''}`,
  })

  if (text) {
    params.append('text', text)
  } else {
    params.append('subset', 'latin')
  }

  const url = `https://fonts.googleapis.com/css2?${params.toString()}`
  const css = await fetch(url).then((res) => res.text())
  const fontUrl = css.match(
    /src: url\((.+)\) format\('(opentype|truetype)'\)/
  )?.[1]

  if (!fontUrl) {
    throw new Error('Font file not found in CSS fetched from Google Fonts')
  }
  return fetch(fontUrl).then((res) => res.arrayBuffer())
}

これで、記事のタイトルに応じて画像を作成できました!  Localhost では、http://localhost:3000/api/og?title=記事のタイトルにアクセスすると、画像を表示できます。

参考サイト

超便利!Next.js 13.4で OGP 画像を自動生成する方法 - Qiita
Webサイトを運用する上で必須のOGP画像をいちいち自分で生成するのは面倒なので、Next.jsでいい感じに作成することができたので解説します。OGP画像とはOGP画像の「OGP」とは「Open…
qiita.com
超便利!Next.js 13.4で OGP 画像を自動生成する方法 - Qiita
Next.jsのImage Generationを利用して、動的にOG画像を作成する
このチュートリアルでは、Next.jsのImage Generationを利用して、動的にOG画像を作成する手順を紹介します。
www.newt.so
Next.jsのImage Generationを利用して、動的にOG画像を作成する

Share