OGP の画像をいちいち作るのが面倒だったので、Next.js の機能を使って生成できるようにしました。
OGP って何?
OGP とは、Open Graph Protocol の略で、ウェブサイトの内容をソーシャルメディアでシェアしたときに表示できるデータのことです。X や Facebook などの SNS でウェブサイトをシェアしたときに、タイトル、説明、画像などが表示されているのを見たことがありますよね。
OGP の目的は、ウェブサイトがソーシャルメディア上でシェアされた際に、適切な形式で表示されるようにすることです。URL だけではどのようなウェブサイトかわかりにくいですが、タイトルや画像などが表示できていれば、どのようなウェブサイトかわかります。
OGP を設定する
OGP は HTML の<head>
内に<meta>
タグとして配置されます。具体的な OGP タグには、以下のようなものがあります。
<meta property="og:title" content="ページのタイトル">
: シェアされたページのタイトルを指定します。<meta property="og:description" content="ページの説明">
: シェアされたページの説明を指定します。<meta property="og:image" content="画像のURL">
: シェアされた際に表示される画像の URL を指定します。
この記事では、画像を Next.js の機能を使って設定していきます。
Next.js の OGP
基本は、Next.js 公式の App Router のチュートリアルを参考にしました。
OGP の設定
まずは、静的なデータを/app/layout.tsx
に記述して設定します。
import { Metadata } from 'next'
const siteName = 'サイトのタイトルです'
const description = 'サイトの説明です'
export const metadata: Metadata = {
title: {
default: siteName,
template: `%s - ${siteName}`,
},
description,
openGraph: {
title: siteName,
description,
siteName,
locale: 'ja_JP',
type: 'website',
images: {
url: `image/ogp.png`,
},
},
}
ページごとに異なる動的なデータは、page.tsx
に記述します。
import { Metadata } from 'next'
export const generateMetadata = async (slug): Promise<Metadata> => {
const post = await getPostBySlug(slug) // ブログの記事データの取得
if (post) {
return {
title: post.title, // ページのタイトル
openGraph: {
title: post.title, // ページのタイトル
description: post.description, // ページの説明
images: {
url: `/api/og?title=${post.title}`, // ページの画像URL
},
},
}
}
return {}
}
これによって、HTML の<head>
内の<meta>
タグにデータが入ります。
<title>サイトのタイトルです</title>
<meta name="description" content="サイトの説明です" />
<meta property="og:title" content="サイトのタイトルです" />
<meta property="og:description" content="サイトの説明です" />
<meta property="og:image" content="https://~" />
これで、OGP を設定できるようになります。
画像については、API を作成して、記事のタイトルから動的に作成できるようにします。
OGP 画像の作成
画像を作成するための API を作成します。/api/og?title=記事のタイトル
にアクセスしたときに、記事のタイトルの入った画像を表示できるようにします。app/api/og/og.tsx
を作り、API を記述していきます。
App Router の API については、公式を参考にしました。
og:image
に入る画像を生成するためには、ImageResponse を使います。ImageResponse に JSX を渡すことで画像を描画できます。
一部省略していますが、画像を生成するソースはこんな感じです。
import { ImageResponse, NextRequest } from 'next/server' // v14.0.0以降ではnext/ogにしましょう。
import { loadGoogleFont } from '@lib/fonts'
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url)
const title = searchParams.get('title') ?? '' // ブログ記事のタイトル
try {
// Google Fontの読み込み
const googleFontArrayBuffer = await loadGoogleFont({
family: 'Mochiy Pop One', // 好きなフォントを設定する。
weight: 400,
text: title,
})
return new ImageResponse(
(
<div
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
textAlign: 'center',
width: 1200,
height: 630,
backgroundImage: 'url(背景画像のURL。省略)',
backgroundSize: '1200px 630px',
backgroundRepeat: 'no-repeat',
}}
>
<div>{title}</div>
</div>
),
{
width: 1200,
height: 630,
fonts: [
{
name: 'MochiyPopOne',
data: googleFontArrayBuffer,
style: 'normal',
weight: 400,
},
],
}
)
} catch (error) {
console.error('Error while generating OGP image:', error)
return new Response(`Failed to generate the image`, {
status: 500,
})
}
}
satori を使っているようですので、使える CSS にはいくつか制約があります。
フォントの読み込み
OGP の画像に Google Fonts を使います。日本語で JIS 第三、四水準漢字対応&かわいいフォントを探しました。
app/lib/font.ts
を作って、GoogleFonts を読み込む処理を記述します。API のファイルに書くとビルド時にエラーが出たのでファイルを分けました。
export async function loadGoogleFont({
family,
weight,
text,
}: {
family: string
weight?: number
text?: string
}) {
const params = new URLSearchParams({
family: `${family}${weight ? `:wght@${weight}` : ''}`,
})
if (text) {
params.append('text', text)
} else {
params.append('subset', 'latin')
}
const url = `https://fonts.googleapis.com/css2?${params.toString()}`
const css = await fetch(url).then((res) => res.text())
const fontUrl = css.match(
/src: url\((.+)\) format\('(opentype|truetype)'\)/
)?.[1]
if (!fontUrl) {
throw new Error('Font file not found in CSS fetched from Google Fonts')
}
return fetch(fontUrl).then((res) => res.arrayBuffer())
}
これで、記事のタイトルに応じて画像を作成できました! Localhost では、http://localhost:3000/api/og?title=記事のタイトル
にアクセスすると、画像を表示できます。