og-image-generator
1
总安装量
1
周安装量
#55307
全站排名
安装命令
npx skills add https://github.com/juanpiriv/x-og-image-generator --skill og-image-generator
Agent 安装分布
opencode
1
Skill 文档
Open Graph Image Generator
Generate high-performance Open Graph images for social media sharing with support for dynamic generation and pre-generated storage patterns.
When to use
Use this skill when:
- Building a web application that needs OG images for social sharing
- Creating meta images for articles, blog posts, or user-generated content
- Optimizing social media previews for Twitter Cards, Facebook, or LinkedIn
- Need both dynamic generation (few pages) and pre-generation (many pages) patterns
- Implementing performance-critical image generation with crawler timeout awareness
Quick Start: Dynamic Generation (3 minutes)
1. Install dependency
npm install @vercel/og
2. Create API route (Next.js)
import { ImageResponse } from '@vercel/og';
export async function GET(request: Request) {
return new ImageResponse(
(
<div style={{
width: '100%',
height: '100%',
display: 'flex',
backgroundColor: '#0a0f1c',
padding: '100px',
fontFamily: 'system-ui, -apple-system, sans-serif',
}}>
<h1 style={{ color: '#fff', fontSize: '64px', fontWeight: 'bold' }}>
Your Title
</h1>
</div>
),
{ width: 2400, height: 1200 } // 2x scale for retina
);
}
3. Add meta tags
<meta property="og:image" content="https://yoursite.com/api/og" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="600" />
<meta name="twitter:card" content="summary_large_image" />
4. Test
Visit: https://cards-dev.twitter.com/validator and paste your URL.
Production Pattern: Pre-Generated Images
For user-generated content or high-traffic pages, pre-generate images when content is created to ensure instant crawler responses.
Pre-generation at content creation
import { ImageResponse } from '@vercel/og';
async function generateAndStoreOgImage(contentData) {
const image = new ImageResponse(
(
<div style={{
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
backgroundColor: '#0a0f1c',
padding: '100px',
justifyContent: 'space-between',
}}>
<h1 style={{ color: '#fff', fontSize: '64px', fontWeight: 'bold' }}>
{contentData.title}
</h1>
<p style={{ color: '#b0b0b0', fontSize: '28px' }}>
{contentData.description}
</p>
</div>
),
{ width: 2400, height: 1200 }
);
const buffer = await image.arrayBuffer();
// Store in database
await db.ogImages.insert({
contentId: contentData.id,
imageData: buffer,
mimeType: 'image/png',
});
return buffer;
}
Serve pre-generated image
export async function getOgImage(contentId: string) {
const { imageData } = await db.ogImages.findOne({ contentId });
return new Response(imageData, {
headers: {
'Content-Type': 'image/png',
'Cache-Control': 'public, max-age=86400', // 24 hours
},
});
}
Express pattern
app.get('/og/:slug.png', async (req, res) => {
try {
const { slug } = req.params;
const content = await db.getContent(slug);
const image = new ImageResponse(
(/* JSX here */),
{ width: 2400, height: 1200 }
);
const buffer = await image.arrayBuffer();
res.setHeader('Content-Type', 'image/png');
res.setHeader('Cache-Control', 'public, max-age=86400');
res.send(Buffer.from(buffer));
} catch (error) {
res.status(500).send('Generation failed');
}
});
Decision: Dynamic vs Pre-Generated
Use Dynamic Generation if:
- Few unique pages (<50)
- Content rarely changes
- Generation time < 2 seconds
- Traffic is low to medium
Use Pre-Generation if:
- Many unique pages (100+)
- User-generated content
- High traffic expected
- Need instant crawler response (avoid timeouts)
Recommended: Pre-generate for production; dynamic for development/testing.
HTML Meta Tags
Include these in your page <head>:
<!-- Open Graph -->
<meta property="og:image" content="https://example.com/og/page.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="600" />
<meta property="og:image:type" content="image/png" />
<meta property="og:title" content="Your Titleâ" /> <!-- Zero-width space required -->
<meta property="og:description" content="Brief description" />
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content="https://example.com/og/page.png" />
<meta name="twitter:title" content="Your Titleâ" />
Important: Include a zero-width space (â) in og:title to prevent crawlers from using default title when og:image is present.
Design Best Practices
Dimensions
- Size: 1200Ã600px (2:1 aspect ratio)
- Rendering: 2400Ã1200px (2x scale for retina quality)
- Safe margins: 50-56px on all sides
- Format: PNG (lossless, predictable)
Visual Design
- High contrast: Text readable at small thumbnail sizes
- Minimal text: 3-5 words maximum for headline
- Brand colors: Consistent palette with your site
- No complex gradients: Use solid colors or simple patterns
Performance
- Generation time: Target <3 seconds (crawlers timeout at ~5s)
- File size: Keep <200KB per image
- Cache: Set to 24 hours minimum (
Cache-Control: public, max-age=86400)
Troubleshooting
| Issue | Solution |
|---|---|
| Twitter shows blank preview | Check: zero-width space in og:title, image is 1200Ã600, URL is public |
| Crawler timeout (X/Twitter) | Switch to pre-generated images; dynamic generation is too slow |
| Image doesn’t load on social media | Verify CDN/storage URL is public; check CORS if cross-origin |
| Generation takes >5 seconds | Reduce complexity; pre-generate instead; use smaller images |
| File size >500KB | Reduce color palette; simplify shapes; avoid complex gradients |
| Meta tags not appearing | Ensure tags are in <head> (not <body>); use valid HTML |
Validation Checklist
- Image dimensions: 1200Ã600px (or 2400Ã1200px for 2x)
- Aspect ratio: 2:1
- Safe margins: 50-56px padding
- High contrast colors (WCAG AA minimum)
- File format: PNG
- File size: <200KB
- Zero-width space in
og:title -
Cache-Controlheader:public, max-age=86400 - Test with Twitter Card Validator: https://cards-dev.twitter.com/validator
- Response time <3 seconds (dynamic) or instant (pre-generated)
- Fallback error handling if generation fails
Implementation Patterns
Next.js with Dynamic Route
// app/api/og/[slug]/route.ts
import { ImageResponse } from '@vercel/og';
export async function GET(
request: Request,
{ params }: { params: { slug: string } }
) {
const content = await getContentBySlug(params.slug);
return new ImageResponse(
(
<div style={{
width: '100%',
height: '100%',
display: 'flex',
backgroundColor: '#0a0f1c',
padding: '100px',
}}>
<h1 style={{ color: '#fff', fontSize: '64px' }}>
{content.title}
</h1>
</div>
),
{ width: 2400, height: 1200 }
);
}
Error Handling Pattern
try {
const image = new ImageResponse(/* ... */);
return new Response(await image.arrayBuffer(), {
headers: { 'Content-Type': 'image/png' },
});
} catch (error) {
console.error('OG generation failed:', error);
// Return fallback or placeholder
return new Response('Image generation failed', { status: 500 });
}
Hybrid Cache Pattern
export async function getOgImage(contentId: string) {
// 1. Check cache
const cached = await cache.get(`og:${contentId}`);
if (cached) return cached;
// 2. Check if generating
if (isGenerating.has(contentId)) {
await waitFor(contentId);
return (await cache.get(`og:${contentId}`));
}
// 3. Generate with timeout
isGenerating.add(contentId);
try {
const buffer = await Promise.race([
generateOgImage(contentId),
timeout(4000), // Crawler safety
]);
await cache.set(`og:${contentId}`, buffer);
return buffer;
} finally {
isGenerating.delete(contentId);
}
}
Common Variants
Minimal Design
const image = new ImageResponse(
(
<div style={{
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#0a0f1c',
padding: '100px',
}}>
<h1 style={{ color: '#fff', fontSize: '72px', textAlign: 'center' }}>
{title}
</h1>
</div>
),
{ width: 2400, height: 1200 }
);
Card Style (Articles)
const image = new ImageResponse(
(
<div style={{
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
backgroundColor: '#0a0f1c',
padding: '100px',
}}>
<div style={{ color: '#00d9ff', fontSize: '24px', fontWeight: '600' }}>
{category}
</div>
<h1 style={{ color: '#fff', fontSize: '64px', fontWeight: 'bold' }}>
{title}
</h1>
<div style={{ color: '#808080', fontSize: '20px' }}>
{publishDate}
</div>
</div>
),
{ width: 2400, height: 1200 }
);
With Image
const image = new ImageResponse(
(
<div style={{ display: 'flex' }}>
<img
src={imageUrl}
style={{
width: '600px',
height: '1200px',
objectFit: 'cover',
}}
alt="Preview"
/>
<div style={{ flex: 1, padding: '100px' }}>
<h1 style={{ color: '#fff', fontSize: '64px' }}>{title}</h1>
</div>
</div>
),
{ width: 2400, height: 1200 }
);
Resources & Links
- Vercel OG Documentation: https://vercel.com/docs/og-image-generation
- Twitter Card Validator: https://cards-dev.twitter.com/validator
- Open Graph Protocol: https://ogp.me
- Twitter Cards Docs: https://developer.twitter.com/en/docs/twitter-for-websites/cards
Summary
- For quick setup: Use dynamic generation pattern above
- For production: Pre-generate images at content creation
- For testing: Use Twitter Card Validator
- For performance: Monitor generation time; cache aggressively
- For crawlers: Always be aware of 5-second timeout; pre-generation is safer
Version
v1.0 | MIT License | Production Ready