Appearance
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 pushUsing 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:
| Secret | Description |
|---|---|
OG_IMAGE_API_KEY | Your 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.jsRate 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)));
}
}
}