作成日時:2024年11月11日 00:23
更新日時:2024年11月11日 00:28
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でも動作します。
※ソースコードを見たい方は下記のリポジトリからどうぞ。
https://github.com/HTNCode/generate-ogp-image-sample
まずは今回わかりやすいように、簡単な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>
);
}
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画像の生成を行うことも可能です。
めちゃくちゃ便利ですね。
あと、よく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