Skip to content

GitHub Actions Integration

Automate OG image generation in your CI/CD pipeline.

Generate Images on Deploy

Add this workflow to .github/workflows/og-images.yml:

yaml
name: Generate OG Images

on:
  push:
    branches: [main]
    paths:
      - 'content/**'  # Only run when content changes
      - 'posts/**'

jobs:
  generate-og-images:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
      
      - name: Generate OG Images
        env:
          OG_API_KEY: ${{ secrets.OG_IMAGE_API_KEY }}
        run: |
          # Generate image for each blog post
          for file in content/posts/*.md; do
            TITLE=$(grep -m1 "^title:" "$file" | cut -d'"' -f2)
            SLUG=$(basename "$file" .md)
            
            curl -X POST https://ogimageapi.io/api/generate \
              -H "Content-Type: application/json" \
              -H "X-API-Key: $OG_API_KEY" \
              -d "{
                \"template\": \"blog\",
                \"title\": \"$TITLE\",
                \"theme\": \"dark\"
              }" \
              --output "public/og/$SLUG.png"
          done
      
      - name: Commit generated images
        run: |
          git config --local user.email "action@github.com"
          git config --local user.name "GitHub Action"
          git add public/og/
          git diff --staged --quiet || git commit -m "Generate OG images"
          git push

Using in Next.js Build

yaml
name: Next.js Build

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Generate OG Images
        env:
          OG_API_KEY: ${{ secrets.OG_IMAGE_API_KEY }}
        run: node scripts/generate-og-images.js
      
      - name: Build Next.js
        run: npm run build
      
      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v25
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}

scripts/generate-og-images.js

javascript
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';

const API_KEY = process.env.OG_API_KEY;
const API_URL = 'https://ogimageapi.io/api/generate';

async function generateImage(post) {
  const response = await fetch(API_URL, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-API-Key': API_KEY
    },
    body: JSON.stringify({
      template: 'blog',
      title: post.title,
      subtitle: post.description,
      author_name: post.author,
      category: post.category,
      theme: 'dark'
    })
  });
  
  return Buffer.from(await response.arrayBuffer());
}

async function main() {
  const postsDir = './content/posts';
  const outputDir = './public/og';
  
  if (!fs.existsSync(outputDir)) {
    fs.mkdirSync(outputDir, { recursive: true });
  }
  
  const files = fs.readdirSync(postsDir).filter(f => f.endsWith('.md'));
  
  for (const file of files) {
    const content = fs.readFileSync(path.join(postsDir, file), 'utf-8');
    const { data } = matter(content);
    const slug = file.replace('.md', '');
    
    console.log(`Generating OG image for: ${data.title}`);
    
    const imageBuffer = await generateImage(data);
    fs.writeFileSync(path.join(outputDir, `${slug}.png`), imageBuffer);
  }
  
  console.log('✅ All OG images generated!');
}

main().catch(console.error);

Secrets Setup

Add these secrets to your GitHub repository:

SecretDescription
OG_IMAGE_API_KEYYour OG Image API key
VERCEL_TOKEN(optional) For Vercel deployments

Go to Settings → Secrets and variables → Actions → New repository secret

Caching Strategy

To avoid regenerating unchanged images:

yaml
- name: Cache OG Images
  uses: actions/cache@v4
  with:
    path: public/og
    key: og-images-${{ hashFiles('content/**') }}
    restore-keys: |
      og-images-

- name: Generate OG Images (if cache miss)
  if: steps.cache.outputs.cache-hit != 'true'
  run: node scripts/generate-og-images.js

Rate Limiting

For large sites, add delays between API calls:

javascript
async function generateWithDelay(posts) {
  for (const post of posts) {
    await generateImage(post);
    await new Promise(r => setTimeout(r, 100)); // 100ms delay
  }
}

Error Handling

javascript
async function generateImage(post, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      const response = await fetch(API_URL, { /* ... */ });
      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      return Buffer.from(await response.arrayBuffer());
    } catch (error) {
      console.error(`Attempt ${i + 1} failed:`, error.message);
      if (i === retries - 1) throw error;
      await new Promise(r => setTimeout(r, 1000 * (i + 1)));
    }
  }
}

Generate stunning Open Graph images programmatically.