本サイトで、OGP の生成を実装した際の実装方法を基に、Next.js App Router で SSG ビルド時に 動的に OGP を生成する方法を紹介します。
以下、Next.js と出てくる場合は、Next.js App Router を指します。
- Next.js App Router で Static Site Generation (SSG) を利用している方
- OGP の生成を実装したい方
- ビルド時に OGP を生成したい方
- Next.js 15.3.2
- @vercel/og (Next.js に含まれています)
OGP とは、Open Graph Protocol の略で、SNS などでシェアされた際に表示される画像やタイトル、説明文などの情報を指定するためのプロトコルです。
以下のような、リンクカードに表示されている情報が OGP の一例です。
OGP で使用される主なメタタグは以下の通りです:
<meta property="og:title" content="ページのタイトル" />
<meta property="og:description" content="ページの説明" />
<meta property="og:type" content="website" />
<meta property="og:url" content="ページのURL" />
<meta property="og:image" content="画像のURL" />
<meta property="og:site_name" content="サイト名" />
Next.js では、 opengraph-image.(jpg|jpeg|png|gif) というファイルを任意のルートに配置することで、静的な OGP を設定することができます。Metadata に自動的に記述されます。
<head><meta property="og:image" content="<generated>" />
<meta property="og:image:type" content="<generated>" />
<meta property="og:image:width" content="<generated>" />
<meta property="og:image:height" content="<generated>" />
<meta name="twitter:image" content="<generated>" />
<meta name="twitter:image:type" content="<generated>" />
<meta name="twitter:image:width" content="<generated>" />
<meta name="twitter:image:height" content="<generated>" />
該当のルートに配置されていない場合は、上位のルートに配置されているファイルが使用されます。
App Router では、@vercel/og が含まれているため、next/og を利用することで、簡単に OGP を生成することができます。
/posts/[slug]/og.png/route.tsx のようなルートで生成することにより、https://example.com/posts/slug/og.png で OGP を取得することが出来ます。
今回の構成では、/api/og などのエッジ関数を利用せずに、ビルド時に OGP を生成することができます。
src/app/posts/[slug]/og.png/route.tsximport path from "path";
import { readFile } from "fs/promises";
import { ImageResponse } from "next/og";
// ビルド時に静的に生成することを指定
export const dynamic = "force-static";
// 生成されなかった場合、404 を返す
export const dynamicParams = false;
interface Post {
slug: string;
title: string;
}
// テスト用のデータ
const testPosts: Post[] = [
{
slug: "test-post-1",
title: "Test Post 1",
},
{
slug: "test-post-2",
title: "Test Post 2",
},
];
// 動的なパラメータを事前に静的に生成するため
export async function generateStaticParams() {
return testPosts.map((post) => ({
slug: post.slug,
}));
}
export async function GET(_: Request, { params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = testPosts.find((post) => post.slug === slug);
if (!post) {
return new Response("Not Found", { status: 404 });
}
try {
// ビルド時に取得可能な画像のURLやデータがない場合、プロジェクト上の画像ファイルを使用したい場合は、以下のようにして読み込むことができます。
const bgData = await readFile(path.join(process.cwd(), "public", "opengraph-image-bg.png"));
const bgSrc = Uint8Array.from(bgData).buffer;
const fontData = await loadFont(post.title);
return new ImageResponse(
<div
style={{
fontFamily: "Noto Sans JP",
fontWeight: 700,
fontSize: 48,
color: "white",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
width: "100%",
height: "100%",
}}
>
<img src={bgSrc} tw="w-full h-full object-cover" />
<div tw="flex flex-col items-center justify-center w-full h-full">
<div tw="text-4xl font-bold text-white">{post.title}</div>
</div>
</div>,
{
fonts: [
{
name: "Noto Sans JP",
data: fontData,
style: "normal",
weight: 700,
},
],
width: 1200,
height: 630,
},
);
} catch (error) {
console.error("OG image generation error:", error);
return new Response("Failed to create OG image", { status: 500 });
}
}
日本語を含む OGP 画像を生成する場合、フォントの明示的な指定が必要です。以下は、Google Fonts からサブセット化したフォントを読み込む実装例です。
src/app/posts/[slug]/og.png/route.tsxexport async function loadFont(subset: string) {
try {
// Google Fonts からサブセット化したフォントを取得
const fontResponse = await fetch(
`https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@700&text=${encodeURIComponent(subset)}`,
);
if (!fontResponse.ok) {
throw new Error("Failed to fetch font CSS");
}
const fontCss = await fontResponse.text();
const fontUrl = fontCss.match(/url\((.*?)\)/)?.[1];
if (!fontUrl) {
throw new Error("Failed to extract font URL");
}
const fontDataResponse = await fetch(fontUrl);
if (!fontDataResponse.ok) {
throw new Error("Failed to fetch font data");
}
const fontData = await fontDataResponse.arrayBuffer();
return fontData;
} catch (error) {
console.error(error);
return new Response("Failed to load font", { status: 500 });
}
}
生成した OGP 画像をサイトに反映するには、以下のようにメタデータを設定します。
src/app/posts/[slug]/page.tsxexport async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata | undefined> {
const { slug } = await params;
return {
openGraph: {
images: [
{
url: `https://example.com/posts/${slug}/og.png`,
width: 1200,
height: 630,
alt: `Post ${slug}`,
type: "image/png",
},
],
},
};
}
-
フォントの読み込み
- 日本語を含む場合は、必ずフォントを明示的に指定する必要があります
- アルファベットのみの際も、私の環境ではエラーが発生しました
- Google Fonts のサブセット化機能を活用することで、必要な文字のみを読み込むことができます
-
画像サイズ
- OGP 画像の推奨サイズは
1200x630 ピクセルです
- SNS によっては、1:1にクロップされる場合が多いため、中心の
630x630 に伝えたい内容を配置することを推奨します
-
ビルド時の生成
dynamic = 'force-static' を指定することで、ビルド時に静的に生成されます
generateStaticParams で生成するパラメータを指定する必要があります
-
エラーハンドリング
- フォントの読み込みや画像生成時のエラーを適切に処理する必要があります
-
style の指定
div の display は必ず flex | block | none のいずれかを明示する必要があります
-
メタデータの設定
- メタデータの設定は、
generateMetadata で行うことができます
- 設定のし忘れや、内容の不備があると、SEO に悪影響を与える可能性があります
Next.js App Router では、ルーティングに少し工夫をすることで、SSG ビルド時に動的な OGP 画像を生成することができます。
日本語フォントの適切な読み込みと、メタデータの設定に注意を払い、適切な画像サイズを設定することで、より良い OGP 画像を生成することができます。