AstroブログでOGP画像を自動生成する方法
SatoriとSharpを使って、Astroブログの記事ごとにOGP画像を動的に生成する方法を紹介します。
fisea blogでは、各記事のOGP(Open Graph Protocol)画像を動的に生成しています。
手動で画像を作成する手間を省き、記事のタイトルやタグ、投稿日などの情報を自動的に反映させるため、Vercel Satori と Sharp を使用した実装を行いました。
今回はその実装方法について解説します。
技術スタック
- フレームワーク: Astro v5
- OGP画像生成: Satori (HTML/CSS から SVG を生成)
- 画像処理: Sharp (SVG を PNG に変換)
- フォント: @fontsource/noto-sans-jp (Google Fonts の Noto Sans JP)
実装のポイント
1. フォントの読み込み
Satori で日本語を描画するには、フォントデータの読み込みが必要です。
このブログでは @fontsource/noto-sans-jp パッケージを使用しています。
ファイルサイズを抑えるため、必要なサブセット(japanese)のみを読み込む工夫をしています。
// src/utils/ogImage.ts
// ローカルの @fontsource/noto-sans-jp から名前付きサブセットを読み込む
function loadNamedSubset(weight: 400 | 700, subset: 'japanese' | 'latin'): ArrayBuffer {
const fontDir = join(
fileURLToPath(import.meta.url),
'../../..',
'node_modules/@fontsource/noto-sans-jp/files'
);
const filePath = join(fontDir, `noto-sans-jp-${subset}-${weight}-normal.woff`);
const buf = readFileSync(filePath);
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) as ArrayBuffer;
}
2. Satori による SVG 生成
generateOgImage 関数がメインの処理を行います。
satori() 関数に、HTMLのようなオブジェクト構造(React Elementのような形式)と、サイズやフォントの設定を渡すことで、SVG文字列が生成されます。
// src/utils/ogImage.ts
export async function generateOgImage(options: OgImageOptions): Promise<Buffer> {
const { title, description, tags = [], publishedAt } = options;
const fonts = getFonts(); // フォントデータの取得(キャッシュ付き)
const svg = await satori(
{
type: 'div',
props: {
style: {
width: '1200px',
height: '630px',
background: '#050e15', // ダークモード背景
display: 'flex',
flexDirection: 'column',
// ... (スタイル定義)
},
children: [
// ... (レイアウト定義: ロゴ、タイトル、タグなど)
],
},
},
{
width: 1200,
height: 630,
fonts,
}
);
// ...
}
デザインは CSS Flexbox を使って構築できます。 このブログのデザインでは、以下の要素を配置しています:
- 背景: ダークカラー(
#050e15)と装飾用のグラデーション円 - ヘッダー: サイトロゴ(SVGパスデータを使用)とブログタグ
- メイン: 記事タイトル(文字数に応じてフォントサイズを自動調整)と説明文
- フッター: 投稿日とアクセントカラーのライン
3. Sharp による PNG 変換
Satori が生成するのは SVG 文字列ですが、OGP 画像として配信するには PNG 形式が一般的です。
ここで sharp を使用して変換を行います。
// src/utils/ogImage.ts
return sharp(Buffer.from(svg)).png().toBuffer();
エンドポイントでの利用
作成したユーティリティ関数は、Astro の API ルートで使用します。
src/pages/ogp/[slug].png.ts というファイルを作成し、各記事ごとの画像URLを生成します。
// src/pages/ogp/[slug].png.ts
export const GET: APIRoute = async ({ props }) => {
const { post } = props;
const png = await generateOgImage({
title: post.data.title,
description: post.data.description,
tags: post.data.tags,
publishedAt: post.data.publishedAt,
});
return new Response(png, {
headers: {
'Content-Type': 'image/png',
// キャッシュ設定も重要
'Cache-Control': 'public, max-age=31536000, immutable',
},
});
};
まとめ
Satori と Sharp を組み合わせることで、高品質な OGP 画像を自動生成する仕組みが構築できました。
一度テンプレートを作ってしまえば、記事を書くたびに画像を作成する必要がなくなり、運用が非常に楽になります。
また、デザインの変更もコードベースで行えるため、メンテナンス性も高いです。