はじめに
本サイトで、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 の基本
OGP とは
OGP とは、Open Graph Protocol の略で、SNS などでシェアされた際に表示される画像やタイトル、説明文などの情報を指定するためのプロトコルです。
以下のような、リンクカードに表示されている情報が OGP の一例です。
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 での OGP 実装方法
1. 静的な OGP の設定
Next.js では、 opengraph-image.(jpg|jpeg|png|gif)
というファイルを任意のルートに配置することで、静的な OGP を設定することができます。Metadata に自動的に記述されます。
<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>" />
該当のルートに配置されていない場合は、上位のルートに配置されているファイルが使用されます。
2. 動的な OGP の生成
App Router では、@vercel/og
が含まれているため、next/og
を利用することで、簡単に OGP を生成することができます。
2.1 基本的な実装
/posts/[slug]/og.png/route.tsx
のようなルートで生成することにより、https://example.com/posts/slug/og.png
で OGP を取得することが出来ます。
今回の構成では、/api/og
などのエッジ関数を利用せずに、ビルド時に OGP を生成することができます。
import 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 });
}
}
2.2 フォントの読み込み
日本語を含む OGP 画像を生成する場合、フォントの明示的な指定が必要です。以下は、Google Fonts からサブセット化したフォントを読み込む実装例です。
export 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 });
}
}
2.3 生成した OGP の適用
生成した OGP 画像をサイトに反映するには、以下のようにメタデータを設定します。
export 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',
},
],
},
};
}
3. 実装時の注意点
-
フォントの読み込み
- 日本語を含む場合は、必ずフォントを明示的に指定する必要があります
- アルファベットのみの際も、私の環境ではエラーが発生しました
- Google Fonts のサブセット化機能を活用することで、必要な文字のみを読み込むことができます
-
画像サイズ
- OGP 画像の推奨サイズは
1200x630
ピクセルです - SNS によっては、1:1にクロップされる場合が多いため、中心の
630x630
に伝えたい内容を配置することを推奨します
- OGP 画像の推奨サイズは
-
ビルド時の生成
dynamic = 'force-static'
を指定することで、ビルド時に静的に生成されますgenerateStaticParams
で生成するパラメータを指定する必要があります
-
エラーハンドリング
- フォントの読み込みや画像生成時のエラーを適切に処理する必要があります
-
style の指定
div
のdisplay
は必ずflex | block | none
のいずれかを明示する必要があります
-
メタデータの設定
- メタデータの設定は、
generateMetadata
で行うことができます - 設定のし忘れや、内容の不備があると、SEO に悪影響を与える可能性があります
- メタデータの設定は、
まとめ
Next.js App Router では、ルーティングに少し工夫をすることで、SSG ビルド時に動的な OGP 画像を生成することができます。
日本語フォントの適切な読み込みと、メタデータの設定に注意を払い、適切な画像サイズを設定することで、より良い OGP 画像を生成することができます。