React + Next.jsで作成したアプリ(フロントエンド)からOGPタグを返却したかったので調査したときのメモです
OGPタグはなぜ必要?
XServerさんの説明はこちら
https://www.xserver.ne.jp/bizhp/open-graph-protocol/
ざっくりな内容として「SNSなどにリンクをシェアしたときに、タイトルや画像を表示するための仕組み」です
今回、趣味開発しているWebアプリに「SNSで投稿内容をシェアするためのURLを発行する」機能を追加するにあたり、X(Twitter)などに画像やタイトルを表示させる必要があったため、OGPタグを追加することにしました。
(ちなみに、自分が作成しているサイトはライダー向けツーリングスポット共有サイトで、マップ上にスポットを投稿できるようなWebサイトです)
↓こんな感じで、Xポスト内に投稿した内容が画像付きで表示されるようになります
ヤギ!柵まで近づいてきてくれて可愛かった!
— ProjectRIDE (@ProjectRID46883) October 1, 2025
https://t.co/YMV9Hki6hV #ProjectRIDE #ツーリング
Next.jsでOGPタグを返却する仕組み
Next.jsで開発するアプリでOGPタグを返却する場合、以下の2通りの方法が利用できます
- metadataオブジェクトを定義し、固定値を返却
- generateMetaData関数を定義し、動的な値を返却
metadataオブジェクトを使用する場合
固定ページなど、動的に情報を取得する必要がない場合metadataオブジェクトをlayout.jsやpage.jsに定義してメタデータ(OGPタグなど)を返却することができます
https://nextjs.org/docs/app/api-reference/functions/generate-metadata#metadata-fields
動的な取得が必要なければ、こちらを利用するのが簡単そうですね!
generateMetada関数を使用する場合
例えば、ユーザーの投稿内容を表示するなど、内容を動的に取得する必要がある場合は「generateMetadata」ファンクションを使用する必要があります
https://nextjs.org/docs/app/api-reference/functions/generate-metadata
自分のサイトでは以下の方法で実装しています。実装先はapp/page.tsxになります
type Props = {
params: Promise<{ id: string }>
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}
// SNSシェアリンク用のOGP生成処理
// https://www.project-ride.com/content_id=1 でアクセスされたときに実行される
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export async function generateMetadata({ params, searchParams }: Props, parent: ResolvingMetadata): Promise<Metadata> {
const resolvedSearchParams = await searchParams
const contentId = resolvedSearchParams.content_id
const wwwBaseUrl = process.env.NEXT_PUBLIC_WWW_BASE_URL ?? ""
const defaultImageUrl = `${process.env.NEXT_PUBLIC_IMAGE_BASE_URL ?? ""}${process.env.NEXT_PUBLIC_NO_IMAGE ?? ""}`
// もし content_id が URL にあれば...
if (typeof contentId === 'string') {
const content = await getContent(contentId);
if (content) {
const imageUrl = content.image_url
? (process.env.NEXT_PUBLIC_IMAGE_BASE_URL ?? "") + content.image_url
: defaultImageUrl;
const title = content.message.replace(/<br\s*\/?>/gi, '').substring(0, 30) + '...';
const description = content.message;
return {
title: title, // タイトル用にメッセージを短縮
description: description,
openGraph: {
title: title,
description: description,
url: `${wwwBaseUrl}?content_id=${contentId}`,
images: [{ url: imageUrl }],
},
twitter: {
card: 'summary_large_image',
title: title,
description: description,
images: [imageUrl],
}
}
}
}
// 有効なコンテンツが見つからない場合、デフォルト内容を返却
return {
title: 'RIDE-Mono',
description: 'バイク乗りのためのおすすめスポット共有サイト',
openGraph: {
title: 'RIDE-Mono',
description: 'バイク乗りのためのおすすめスポット共有サイト',
url: wwwBaseUrl,
images: [{ url: "" }], // デフォルト画像
}
}
}
スクリプト内のgetContent関数でユーザー投稿内容を取得し、メタデータを設定しています
注意点としてgenerateMetadata関数はサーバーコンポーネントで実行する必要があります
自分が最初作成したとき、クライアントコンポーネントにてページを作成していたため、generateMetadata関数を利用するために、既存のページ内容を別途クライアントコンポーネントのファイルに別出しして対応を行いました
export default function Home() {
return (<TopPage />);
}
もともと記載していた内容をTopPageコンポーネントに分離しています
TopPageコンポーネントでは、’use client’を利用し、クライアントコンポーネントとして動作させています
プレビューが出ない時がある
生成したリンクをX(Twitter)に貼り付けたときにプレビューが表示されたり、されなかったりといった症状がでてデバッグに苦戦しました
実際には投稿後に正しくOGPの内容が表示されたので、投稿前のプレビューの精度は参考程度に考えていたほうが良いのかもしれません(逆に投稿したのに、想定したOGPの内容が表示されない場合は、何かを間違えている可能性が高い)
以上です
ということでNext.jsからMetadata(OGPタグ)を返却する方法でした
SPAだとどうやってMetadataを返却しているのか正直謎でしたが、調べてみるときちんと仕組みが備わっているんですね!
Metadataを生成することで、SNSシェア以外にもクローラー対応など、色々できることが増えそうです