AstroブログでOGP画像を自動生成する方法

SatoriとSharpを使って、Astroブログの記事ごとにOGP画像を動的に生成する方法を紹介します。

fisea blogでは、各記事のOGP(Open Graph Protocol)画像を動的に生成しています。

手動で画像を作成する手間を省き、記事のタイトルやタグ、投稿日などの情報を自動的に反映させるため、Vercel SatoriSharp を使用した実装を行いました。

今回はその実装方法について解説します。

技術スタック

  • フレームワーク: 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 画像を自動生成する仕組みが構築できました。

一度テンプレートを作ってしまえば、記事を書くたびに画像を作成する必要がなくなり、運用が非常に楽になります。

また、デザインの変更もコードベースで行えるため、メンテナンス性も高いです。

記事一覧に戻る