フリーランスWebエンジニア HTNCode

▲
  • HOME
  • ABOUT
  • SERVICE
  • ACHIEVEMENT
  • BLOG
  • CONTACT
  • githubIcon
  • twitterIcon
プライバシーポリシー
footerLogoImage

© HTNCode All Rights Reserved.

eyecatch

動的なOGP画像の生成方法

作成日時:2024年11月11日 00:23

更新日時:2025年1月29日 02:06

Next.js

React

はじめに

今回は動的なOGP画像の生成方法について触れたいと思います。
公式ドキュメントにも記載があるのですが、実際に実装する際にちょっと苦戦したので備忘録としても。

 

環境

・Next.js 15.0.3
・React 19.0.0-rc
・next/og(ImageResponse)
・Edge Runtime
・tailwind.css

※Next.js15なのはちょっと試しに触ってみたついでです。Next.js14でも動作します。 

page.tsx

まずは今回わかりやすいように、簡単なUIとしてpage.tsxでタイトルと説明文を入力し、動的にOGP画像とURLが表示されるものを作りました。
実際はOGP画像なので、SNSにシェアする際に生成したURLなりを加工してシェアしたり、といった感じで使うことが多いと思います。

 

// page.tsx

"use client";
import Image from "next/image";
import { useState } from "react";
import { generateOgpImage } from "./actions";

export default function Home() {
  const [imageUrl, setImageUrl] = useState<string | null>(null);
  const [isPending, setIspendig] = useState(false);

  const handleSubmit = async (formData: FormData) => {
    setIspendig(true);
    const title = formData.get("title") as string;
    const description = formData.get("description") as string;
    const data = {
      title,
      description,
    };
    const imageUrl = await generateOgpImage(data);
    if (imageUrl) {
      setImageUrl(imageUrl);
    }
    setIspendig(false);
  };

  return (
    <div className="max-w-3xl mx-auto p-6 font-sans">
      <h1 className="text-4xl font-bold text-center text-gray-800 mb-8">
        動的なOGP画像の生成
      </h1>
      <section className="bg-gray-100 rounded-lg p-6 shadow-md">
        <h2 className="text-2xl font-semibold text-gray-700 mb-4">
          任意のOGP画像を生成します
        </h2>
        <article>
          <form action={handleSubmit} className="space-y-4">
            <input
              type="text"
              name="title"
              placeholder="タイトル"
              className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
            />
            <input
              type="text"
              name="description"
              placeholder="説明文"
              className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
            />
            <button
              type="submit"
              className="w-full bg-blue-500 text-white py-2 px-4 rounded-md hover:bg-blue-600 transition duration-300 ease-in-out"
              disabled={isPending}
            >
              {isPending ? "生成中・・・" : "生成"}
            </button>
          </form>
          {imageUrl && (
            <>
              <div className="mt-4 w-full">
                <Image
                  src={imageUrl}
                  alt="OGP画像"
                  width={1200}
                  height={630}
                  layout="responsive"
                  className="rounded-lg shadow-md"
                />
              </div>
              <div>
                <p>画像URL: {imageUrl}</p>
              </div>
            </>
          )}
        </article>
      </section>
    </div>
  );
}

 

actions.ts

getUrlはおまけなので、気になる方は下記のリポジトリからソースコードをご覧ください。ただ単にlocalhostなのか本番用URLなのかを切り分けて組めるようにしてあるだけです。

 

本題はここからです。まず、appディレクトリ直下でapi/ogディレクトリを作成し、route.tsxファイルを作成します。
ここで、next/ogを使って、リクエストに含まれるタイトルと説明文を利用した動的な画像を生成し返却します。

 

// api/og/route.tsx

import { ImageResponse } from "next/og";
import { NextRequest } from "next/server";

export const runtime = "edge";

export async function POST(request: NextRequest) {
  try {
    const { title, description } = await request.json();

    return new ImageResponse(
      (
        <div
          style={{
            width: "100%",
            height: "100%",
            display: "flex",
            flexDirection: "column",
            alignItems: "center",
            justifyContent: "center",
            backgroundColor: "#fff5d6",
            fontFamily: "sans-serif",
            border: "32px #f3971a solid",
          }}
        >
          <h1
            style={{
              fontSize: "64px",
              fontWeight: "bold",
              color: "#1f2937",
              marginBottom: "24px",
              textAlign: "center",
            }}
          >
            {title}
          </h1>
          <p
            style={{
              fontSize: "32px",
              color: "#4b5563",
              textAlign: "center",
              maxWidth: "80%",
            }}
          >
            {description}
          </p>
        </div>
      ),
      {
        width: 1200,
        height: 630,
      }
    );
  } catch (error) {
    return new Response(`動的なOGP画像の生成ができませんでした: ${error}`, {
      status: 500,
    });
  }
}

 

page.tsxは今回クライアントコンポーネントとして利用したかったので、actions.tsを別につくって、generateOgpImage関数を定義、そこからapiをフェッチするようにしています。
apiからの返却値をblobとして受け取り、URL.createObjectURLでimageUrlを生成し、page.tsxに返却します。

 

// actions.ts

import { getUrl } from "@/app/utils/getUrl";

export async function generateOgpImage(data: {
  title: string;
  description: string;
}): Promise<string | null> {
  try {
    const baseUrl = getUrl();
    const res = await fetch(`${baseUrl}/api/og`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify(data),
    });

    if (!res.ok) {
      throw new Error("OGP画像の生成に失敗しました。");
    }

    const blob = await res.blob();
    const imageUrl = URL.createObjectURL(blob);
    return imageUrl;
  } catch (error) {
    console.log(error);
    return null;
  }
}

 

これで、page.tsx側でそれを受け取って「生成」ボタンが押されるたびに動的なOGP画像が表示されます。
同じ要領で画像を配置したり、CSSでいろいろできますね。

  

  

 

補足

ちなみに、今回はSNSへのシェアを想定した動的なOGP画像の生成の実装方法を紹介しましたが、
公式ドキュメントに記載のある通り、ディレクトリごとに「opengraph-image.tsx」を配置して、同じようにnext/ogを使った動的なOGP画像の生成を行うことも可能です。
めちゃくちゃ便利ですね。

  

Satoriとの違い

あと、よくnext/ogと比較される話題のsatoriとどう違うの?と気になったので簡単に調べてみました。
Next.js使うならnext/ogが使いやすいみたいですが、出力形式をSVGにしたかったりより柔軟にカスタマイズしたい時などはSatoriを使う、という感じでしょうか。Satoriは使ったことがないので、使う機会あればこちらもご紹介したいと思います。

  

特徴

next/og

satori

開発元

Vercel

Vercel

主な用途

Next.jsでの動的OG画像生成

HTML/CSSからSVG生成

動作環境

Edge Runtime

環境非依存(Node.js, ブラウザ等)

Next.jsとの統合

App Routerに標準搭載

別途インストールが必要

出力形式

PNG

SVG

API

ImageResponseクラス

satori関数

CSSサポート

制限あり

制限あり

フォント対応

ttf, otf, woff

ttf, otf, woff

画像埋め込み

URL指定可能

データURL形式推奨

使用ライブラリ

satoriとresvgを内部で使用

単体で使用可能

カスタマイズ性

制限あり

より柔軟

 

それではまた。

 

HTNCode