GitHub Actions

OGP画像を
自動生成する

ブログ記事やドキュメントのOGP画像を、pushのたびにGitHub Actionsで自動生成。Satori、Pillow、統合パターンまで、コピペで使えるワークフローをまとめました。

ワークフローを見る →
3 パターン · 日本語対応 · コピペ対応

なぜ Actions で生成する?

手動でOGP画像を作る手間をなくし、記事追加のたびに自動で最新の画像を生成できます。

自動化

記事を push するだけでOGP画像が自動生成・コミットされます。手作業はゼロ。

🎨

デザイン統一

全記事で同じテンプレート・フォント・配色を使えます。ブランド感を保てます。

🇯🇵

日本語対応

Noto Sans JP などのフォントを Actions 上で自動ダウンロード・埋め込み。

📦

Pages と統合

サイトビルド前にOGP生成を挟むだけ。1つのワークフローで完結します。


ワークフロー集

用途に合わせて選べる3パターン。YAMLをコピーしてそのまま使えます。

01 Satori + Resvg 推奨

SVGテンプレートから高速にPNGを生成。Puppeteerより軽量で、CSSライクなレイアウトが可能です。Node.jsプロジェクトに最適です。

.github/workflows/generate-ogp.yml
name: Generate OGP Images

on:
  push:
    branches: ["main"]
    paths:
      - "content/**"
      - "scripts/ogp/**"
  workflow_dispatch:

jobs:
  ogp:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      - run: npm ci

      - name: Download Japanese font
        run: |
          mkdir -p scripts/ogp/fonts
          curl -sL -o scripts/ogp/fonts/NotoSansJP-Bold.otf \
            "https://github.com/googlefonts/noto-cjk/raw/main/Sans/OTF/Japanese/NotoSansJP-Bold.otf"

      - name: Generate OGP images
        run: node scripts/ogp/generate.js

      - name: Commit generated images
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add public/ogp/
          if git diff --cached --quiet; then
            echo "No changes"
            exit 0
          fi
          git commit -m "chore: update ogp images [skip ci]"
          git push
scripts/ogp/generate.js の例
const fs = require('fs');
const path = require('path');
const satori = require('satori').default;
const { Resvg } = require('@resvg/resvg-js');

const fontData = fs.readFileSync(path.join(__dirname, 'fonts/NotoSansJP-Bold.otf'));

const posts = [
  { slug: 'hello-world', title: 'GitHub Actions で OGP 画像を自動生成する', date: '2026-08-25' },
];

async function generate() {
  const outDir = path.join(process.cwd(), 'public/ogp');
  fs.mkdirSync(outDir, { recursive: true });

  for (const post of posts) {
    const svg = await satori(
      {
        type: 'div',
        props: {
          style: {
            width: 1200, height: 630,
            background: 'linear-gradient(135deg, #0d1117, #161b22)',
            color: '#c9d1d9', fontFamily: 'Noto Sans JP',
            padding: 60, display: 'flex',
            flexDirection: 'column', justifyContent: 'center',
          },
          children: [
            {
              type: 'div',
              props: {
                style: { fontSize: 56, fontWeight: 700, lineHeight: 1.4, color: '#58a6ff' },
                children: post.title,
              },
            },
            {
              type: 'div',
              props: {
                style: { marginTop: 40, fontSize: 28, color: '#8b949e' },
                children: post.date,
              },
            },
          ],
        },
      },
      {
        width: 1200, height: 630,
        fonts: [{ name: 'Noto Sans JP', data: fontData, weight: 700 }],
      }
    );

    const resvg = new Resvg(svg, { fitTo: { mode: 'original', original: 1 } });
    const pngData = resvg.render();
    fs.writeFileSync(path.join(outDir, `${post.slug}.png`), pngData.asPng());
  }
}

generate().catch(console.error);
package.json に追加する依存
{
  "devDependencies": {
    "satori": "^0.12.0",
    "@resvg/resvg-js": "^2.6.2"
  }
}
ポイント:paths フィルタで content ディレクトリ変更時のみ実行し、無駄なビルドを防ぎます。[skip ci] を入れて無限ループを回避しています。

02 Pillow シンプル

Node.js を使わず、Pythonだけで完結したい場合。依存が少なく、軽量に動作します。

.github/workflows/generate-ogp-pillow.yml
name: Generate OGP with Pillow

on:
  push:
    branches: ["main"]
    paths:
      - "content/**"
      - "scripts/generate_ogp.py"
  workflow_dispatch:

jobs:
  ogp:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"

      - run: pip install pillow

      - name: Download font
        run: |
          mkdir -p fonts
          curl -sL -o fonts/NotoSansJP-Bold.otf \
            "https://github.com/googlefonts/noto-cjk/raw/main/Sans/OTF/Japanese/NotoSansJP-Bold.otf"

      - run: python scripts/generate_ogp.py

      - name: Commit
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add public/ogp/
          if git diff --cached --quiet; then exit 0; fi
          git commit -m "chore: update ogp images [skip ci]"
          git push
scripts/generate_ogp.py の例
from PIL import Image, ImageDraw, ImageFont
import os

os.makedirs("public/ogp", exist_ok=True)

posts = [
    {"slug": "hello-world", "title": "GitHub Actions で OGP 画像を自動生成する"},
]

W, H = 1200, 630
title_font = ImageFont.truetype("fonts/NotoSansJP-Bold.otf", 56)
meta_font  = ImageFont.truetype("fonts/NotoSansJP-Bold.otf", 28)

for post in posts:
    img = Image.new("RGB", (W, H), "#0d1117")
    draw = ImageDraw.Draw(img)
    draw.text((60, 200), post["title"], fill="#58a6ff", font=title_font)
    draw.text((60, 500), "2026-08-25", fill="#8b949e", font=meta_font)
    img.save(f"public/ogp/{post['slug']}.png")

03 Pages デプロイ前に統合 統合

OGP生成とサイトビルド・Pagesデプイを1つのワークフローにまとめます。成果物管理がシンプルになります。

.github/workflows/pages.yml
name: Build, Generate OGP, and Deploy

on:
  push:
    branches: ["main"]

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: true

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      # --- OGP生成 ---
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci

      - name: Download font
        run: |
          mkdir -p scripts/ogp/fonts
          curl -sL -o scripts/ogp/fonts/NotoSansJP-Bold.otf \
            "https://github.com/googlefonts/noto-cjk/raw/main/Sans/OTF/Japanese/NotoSansJP-Bold.otf"

      - run: node scripts/ogp/generate.js

      # --- サイトビルド ---
      - run: npm run build   # Vite / Astro / Next.js など

      # OGP画像を成果物にコピー
      - run: mkdir -p dist/ogp && cp -r public/ogp/* dist/ogp/ || true

      - uses: actions/configure-pages@v5
      - uses: actions/upload-pages-artifact@v3
        with:
          path: ./dist

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - uses: actions/deploy-pages@v4
        id: deployment

ポイントまとめ

比較表

方式速度表現力向き
Satori + Resvg速い高い(CSSライク)Node.js プロジェクト
Pillow速い低〜中Python / 軽量志向
Puppeteer遅い最高(HTML/CSS)複雑なレイアウト

よくある注意点

  • OGP推奨サイズは 1200 x 630 px
  • 日本語フォントは Actions runner に入っていないので必ずダウンロードまたはリポジトリに含める
  • コミットメッセージに [skip ci] を入れて無限ループを防ぐ
  • paths フィルタで content 変更時のみ実行すると無駄なビルドを減らせる

さっそく試してみる

上のYAMLをコピーして .github/workflows/ に貼り付けるだけで始められます。

ワークフローを選ぶ →