概要
ブログの記事を多言語で提供した後、言語を変える時、テキストは変わってもサムネイルは変わらず、内容は英語なのに画像はハングルで表示される場合があり、違和感がありました。これを理由に画像を動的に生成することが必要だと思い調べていたらvercelで提供してるライブラリがあったので使ってみました。
Next.jsで動的OGイメージを設定する
Next.jsはVercelの@vercel/og
ライブラリを使って動的なOGイメージ生成をサポートします。この機能はVercel Functionsを使って計算して実行されます。
インストール方法
- App Routerプロジェクト: 追加パッケージのインストールは不要(内蔵されている)
import { ImageResponse } from "next/og"
- Pages Routerプロジェクト:
npm i @vercel/og
コマンドでインストールimport { ImageResponse } from "@vercel/og
基本的な使い方
スタイリング
普通にコンポーネントを作るように作ればいいです。JSX文法を使って作るのでapiですが、ファイル拡張子をjsx
もしくはtsx
で作成する必要があります。
下記のようにstyleを直接挿入して生成することができます、
return new ImageResponse(
<div
style={{
height: '100%',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#fff',
fontSize: 32,
fontWeight: 600,
}}
>
<div style={{ marginTop: 40 }}>Hello, World</div>
</div>
)
tw
というpropsを使ってtailwindCSS文法を適用することもできます。
return new ImageResponse(
<div
tw="h-full w-full flex flex-col items-center justify-center bg-white text-xl font-bold"
>
<div tw="mt-10">Hello, World</div>
</div>
)
Pages Routerプロジェクト
/pages/api/og.tsx
のパスにファイルを作って次のように記述します。
import { NextRequest } from "next/server";
import { ImageResponse } from "@vercel/og";
export const config = {
runtime: "edge",
};
export default async function handler(request: NextRequest) {
const { searchParams } = request.nextUrl;
const title = searchParams.get("title");
return new ImageResponse(
(
<div tw="flex flex-col w-full h-full items-center justify-center bg-white">
<div tw="bg-gray-50 flex w-full">
<div tw="flex flex-col md:flex-row w-full py-12 px-4 md:items-center justify-between p-8">
<h2 tw="flex flex-col text-3xl sm:text-4xl font-bold tracking-tight text-gray-900 text-left">
{title}
</h2>
<div tw="mt-8 flex md:mt-0">
<div tw="flex rounded-md shadow">
<a tw="flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-5 py-3 text-base font-medium text-white">
시작하기
</a>
</div>
<div tw="ml-3 flex rounded-md shadow">
<a tw="flex items-center justify-center rounded-md border border-transparent bg-white px-5 py-3 text-base font-medium text-indigo-600">
더 알아보기
</a>
</div>
</div>
</div>
</div>
</div>
),
{
width: 1200,
height: 630,
},
);
}
Pages RoutrerはNode.js runtimeをサポートしてないので、Vercel FunctionがEdge runtimeで実行されるように下記の設定を追加で適用する必要があります。
export const config = {
runtime: "edge",
};
App Routerプロジェクト
/app/api/og/route.tsx
ファイルを作って下記のように作成します。Pages Routerとパスや作成方法が違うだけで、返される値は同じです。
export async function GET(request: NextRequest) {
const { searchParams } = request.nextUrl;
const title = searchParams.get("title");
// 이하 생략...
}
コードで見るようにsearchParams
を使って動的に値を取得して表示することができます。
const title = searchParams.get("title");
このように次のAPIを呼び出した時、ブラウザに表示される結果は次のようになります。/api/og?title=WOW
カスタムフォントを追加する
もっと素敵なOGイメージを作るため、カスタムフォントを追加してみましょう。フォントファイルが/public/fonts/
の下にあるとしたら、次のように作成することができます。
import { NextRequest } from "next/server";
import { ImageResponse } from "next/og";
import { join } from "path";
import { readFileSync } from "fs";
export async function GET(request: NextRequest) {
// 생략...
const fontPath = join(process.cwd(), "public", "fonts", "WONTitle.ttf");
const fontData = readFileSync(fontPath);
return new ImageResponse(
(
<div>
// 생략...
</div>
),
{
width: 1200,
height: 630,
fonts: [
{
name: "WONTitle",
data: fontData,
},
],
},
);
}
画像を追加する
画像を呼び出して追加することもできます。外部URLがある場合、そのURLをそのまま入れればいいし、ローカルにある画像を使う場合、下記のように使うことができます。
const logoPath = join(
process.cwd(),
"public",
"images",
"logo",
`${logo}.png`,
);
const logoBuffer = readFileSync(logoPath);
const logoBase64 = `data:image/png;base64,${logoBuffer.toString("base64")}`;
// ... 생략
return new ImageResponse((
<img
alt="og thumbnail"
tw="absolute -translate-x-1/2 -translate-y-1/2 "
src={logoBase64}
width={logoWidth}
height={logoHeight}
style={{
opacity: 0.6,
}}
/>), ...)
結果
これで、言語によってスタイルは同じでも、テキストを変えてサムネイルを動的に生成して表示できるようになりました。毎回サムネイルを生成するために画像エディタに入る必要がありましたが、これからはその必要はないようです。
🔎参考 参照