Next.jsでOGP画像を動的に生成する方法
2024-07-12
調べてみると色々と情報が錯綜していたので、備忘録がてら。
今回は以下の仕様となっています。
- Next.js
- Vercel
適当に router.tsx ファイルを作成し、以下のように書いてあげたら動きました。
import { promises as fs } from "fs";
import path from "path";
import { ImageResponse } from "@vercel/og";
import { NextRequest } from "next/server";
import parseMD from "parse-md";
function getUniqueChars(text: string): string {
const charSet = new Set<string>();
for (const char of text) {
charSet.add(char);
}
return Array.from(charSet).join("");
}
type GetArticleParams = {
slug: string;
};
type GetArticleData = {
title: string;
};
async function getArticle({ slug }: GetArticleParams): Promise<GetArticleData> {
const markdownPath = path.join(
process.cwd(),
"/src/markdown-pages",
`${slug}.md`,
);
const fileContents = await fs.readFile(markdownPath, "utf8");
const { metadata } = parseMD(fileContents);
const { title } = metadata as {
title: string;
};
return { title };
}
type Context = {
params: { slug: string };
};
export async function GET(
_: NextRequest,
{ params: { slug } }: Context,
): Promise<ImageResponse> {
const { title } = await getArticle({ slug });
const fontData = await fetch(
`https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@700&text=${encodeURIComponent(
getUniqueChars(title),
)}`,
).then((res) => res.text());
const fontUrl = fontData.match(/url\((.*?)\)/)?.[1];
if (!fontUrl) {
throw new Error("Failed to load font");
}
const font = await fetch(fontUrl).then((res) => res.arrayBuffer());
const imageIndex = (parseInt(slug, 10) % 2) + 1;
return new ImageResponse(
(
<div
style={{
background: `url('https://hogefuga/piyomoge.png')`,
display: "flex",
height: "100%",
padding: "30px 36px",
width: "100%",
}}
>
<div
style={{
alignItems: "center",
color: "#fff",
display: "flex",
fontFamily: '"Noto Sans JP", sans-serif',
fontSize: "42px",
fontWeight: "bold",
height: "44%",
justifyContent: "center",
padding: "0 36px",
textAlign: "center",
width: "100%",
}}
>
{title.split("\n").map((line, i) => (
<div key={i}>{line}</div>
))}
</div>
</div>
),
{
debug: false,
fonts: [
{
data: font,
name: "Noto Sans JP",
style: "normal",
weight: 700,
},
],
height: 630,
width: 1200,
},
);
}
今回は静的に配置されたマークダウンファイルから OGP 画像を動的に生成しているのでちょっと大げさですが、大まかな実装感は変わらないかなと。
Google Fonts を噛ませて日本語フォントに対応しましたが、サブセット化はきっちり行いましょう。