2024/02/28

Next.js App Routerで動的にOpenGraphの画像を生成する

本ブログで、動的に画像を生成するための方法を共有します。

はじめに

OpenGraphは、TwitterやDiscord、SlackなどのSNSでリンクを共有した際に、 リンク先のサイトの情報を表示するためのプロトコルです。 QiitaやZennなどはページのタイトルが画像に入っていると思いますが、 今回はそれと同じような画像をNext.jsで動的に生成する方法を紹介します。

やり方

やり方としては、opengraph-imageで生成する方法と、route.tsxを使う方法があります。

今回はroute.tsxを使う方法を紹介します。 普通のルートで生成する方法は、 Next.js公式ドキュメントのこちら を参照してください。

apiルートを使う場合はNext.js公式ドキュメントにはなく、Vercelの公式ドキュメントに書いてありました。 今回はこちらをもとに進めていきます。

api側の実装

まずは、画像生成するapiルートを作成します。 srcディレクトリを使っていない方は、読み替えてください。

mkdir -p src/app/api/og
touch src/app/api/og/route.tsx

公式にはこのように書いてあります。

import { ImageResponse } from 'next/og';
// App router includes @vercel/og.
// No need to install it.

export const runtime = 'edge';

export async function GET() {
  return new ImageResponse(
    (
      <div
        style={{
          fontSize: 40,
          color: 'black',
          background: 'white',
          width: '100%',
          height: '100%',
          padding: '50px 200px',
          textAlign: 'center',
          justifyContent: 'center',
          alignItems: 'center',
        }}
      >
        👋 Hello
      </div>
    ),
    {
      width: 1200,
      height: 630,
    },
  );
}

めっちゃ簡単ですね。

ただ、edge runtimeなので、ここではNodeのAPIは使えないことに注意が必要です。 これはroute.tsxを使わなくても同じです。

自分の場合はsearchParamsに文字の情報を渡して実装しました。

また、画像についてはVercelがプレイグラウンドを提供しているので、 自分はそれを使ってスタイリングしました。

https://og-playground.vercel.app/

ただ、フォントは異なるので、注意が必要です。 あとサイズもデフォルトだと800x400なので、各自変更してください。 自分は1200x630にしました。

自分の実装はこんな感じです。

publicディレクトリにフォントファイルを置いて、それをfetchしています。

Next.js v16に上げた段階でエラーがでてしまい確認したところ、 公式ドキュメントでGoogle Fontsをfetchする形に変わっていました。

呼び出し側の実装

今回はホームと各記事の2種類から呼び出そうと思います。

ホーム

まずはホームです。

src/app/layout.tsx
import type { Metadata } from 'next';
...

export const metadata: Metadata = {
  metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL ?? 'http://localhost:3000'),
  title: 'RUNFUNRUN.dev',
  description: 'This is my tech blog.',
  openGraph: {
    title: 'RUNFUNRUN.dev',
    description: 'This is my tech blog.',
    images: '/api/og',
    url: '/',
  },
  twitter: {
    title: 'RUNFUNRUN.dev',
    description: 'This is my tech blog.',
    images: '/api/og',
  },
};

...

searchParamsを設定してないので、先ほど実装したようにタイトルが ドデカくなります。

home ogp image

記事ページ

記事の方はpage.tsxgenerateMetadataで設定します。

getPagegetPagesはFumadocsの機能で、 mdxのデータを取ってきてくれる関数です。

Next.jsのMetadataではmetadataBaseを設定することで、 プロパティ内で相対パスを使うことができます。

src/app/posts/[[...slug]]/page.tsx
import { getPage, getPages } from '@/app/source';
import type { Metadata } from 'next';
...

export const generateMetadata = ({ params }: { params: { slug?: string[] } }) => {
  const post = getPage(params.slug);

  if (post === undefined) return;

  const title = post.data.title;
  const description = post.data.description;
  const imageParams = new URLSearchParams();
  imageParams.set('title', title);
  imageParams.set('description', description ?? '');

  return {
    metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL ?? 'http://localhost:3000'),
    title,
    description,
    openGraph: {
      title,
      description,
      images: `/api/og?${imageParams}`,
      url: post.url,
    },
    twitter: {
      title,
      description,
      images: `/api/og?${imageParams}`,
    },
  } satisfies Metadata;
};

...

記事のページはこんな感じです。

post ogp image

最後に

結構簡単に実装できました。 Next.jsは本当に便利です。

ソースコードを全部見たい方は、本ブログ右上からGitHubリポジトリを 見てみてください。

ではまた。

最終更新日:2026/01/27