Appearance
Next.js Integration
Generate dynamic OG images for your Next.js application.
Quick Start
1. Install Dependencies
bash
npm install
# No additional packages needed - uses native fetch2. Set Environment Variable
bash
# .env.local
OG_IMAGE_API_KEY=og_your_api_key_here3. Create API Route
javascript
// app/api/og/route.js (App Router)
import { NextResponse } from 'next/server';
export async function GET(request) {
const { searchParams } = new URL(request.url);
const title = searchParams.get('title') || 'Default Title';
const subtitle = searchParams.get('subtitle') || '';
const response = await fetch('https://ogimageapi.io/api/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.OG_IMAGE_API_KEY
},
body: JSON.stringify({
title,
subtitle,
template: 'blog',
theme: 'dark'
})
});
const imageBuffer = await response.arrayBuffer();
return new NextResponse(imageBuffer, {
headers: {
'Content-Type': 'image/png',
'Cache-Control': 'public, max-age=86400, s-maxage=86400'
}
});
}4. Use in Metadata
javascript
// app/blog/[slug]/page.js
export async function generateMetadata({ params }) {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [
{
url: `/api/og?title=${encodeURIComponent(post.title)}&subtitle=${encodeURIComponent(post.excerpt)}`,
width: 1200,
height: 630
}
]
},
twitter: {
card: 'summary_large_image',
title: post.title,
description: post.excerpt,
images: [`/api/og?title=${encodeURIComponent(post.title)}`]
}
};
}Static Generation (Recommended)
For best performance, generate images at build time:
javascript
// scripts/generate-og-images.js
const fs = require('fs');
const path = require('path');
async function generateOGImages() {
const posts = await getAllPosts();
for (const post of posts) {
const response = await fetch('https://ogimageapi.io/api/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.OG_IMAGE_API_KEY
},
body: JSON.stringify({
title: post.title,
subtitle: post.excerpt,
author_name: post.author,
template: 'blog',
theme: 'dark'
})
});
const buffer = await response.arrayBuffer();
const outputPath = path.join('./public/og', `${post.slug}.png`);
fs.writeFileSync(outputPath, Buffer.from(buffer));
console.log(`Generated: ${outputPath}`);
}
}
generateOGImages();Add to package.json:
json
{
"scripts": {
"generate-og": "node scripts/generate-og-images.js",
"build": "npm run generate-og && next build"
}
}App Router (Next.js 13+)
Dynamic Route Handler
javascript
// app/api/og/[...slug]/route.js
import { NextResponse } from 'next/server';
export async function GET(request, { params }) {
const slug = params.slug?.join('/') || '';
const post = await getPostBySlug(slug);
if (!post) {
return NextResponse.json({ error: 'Not found' }, { status: 404 });
}
const response = await fetch('https://ogimageapi.io/api/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.OG_IMAGE_API_KEY
},
body: JSON.stringify({
title: post.title,
subtitle: post.excerpt,
author_name: post.author.name,
author_avatar_url: post.author.avatar,
template: 'blog',
theme: 'dark'
})
});
return new NextResponse(await response.arrayBuffer(), {
headers: {
'Content-Type': 'image/png',
'Cache-Control': 'public, max-age=86400'
}
});
}With generateStaticParams
javascript
// app/blog/[slug]/page.js
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map((post) => ({ slug: post.slug }));
}
export async function generateMetadata({ params }) {
const post = await getPost(params.slug);
return {
openGraph: {
images: [`/og/${params.slug}.png`]
}
};
}Pages Router (Next.js 12)
API Route
javascript
// pages/api/og.js
export default async function handler(req, res) {
const { title, subtitle } = req.query;
const response = await fetch('https://ogimageapi.io/api/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.OG_IMAGE_API_KEY
},
body: JSON.stringify({
title: title || 'My Site',
subtitle: subtitle || '',
template: 'default',
theme: 'dark'
})
});
const buffer = await response.arrayBuffer();
res.setHeader('Content-Type', 'image/png');
res.setHeader('Cache-Control', 'public, max-age=86400');
res.send(Buffer.from(buffer));
}In getStaticProps
javascript
// pages/blog/[slug].js
import Head from 'next/head';
export async function getStaticProps({ params }) {
const post = await getPost(params.slug);
// Generate OG image at build time
const ogResponse = await fetch('https://ogimageapi.io/api/generate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.OG_IMAGE_API_KEY
},
body: JSON.stringify({
title: post.title,
subtitle: post.excerpt,
template: 'blog',
theme: 'dark'
})
});
const fs = require('fs');
const buffer = await ogResponse.arrayBuffer();
fs.writeFileSync(`./public/og/${params.slug}.png`, Buffer.from(buffer));
return { props: { post } };
}
export default function BlogPost({ post }) {
return (
<>
<Head>
<meta property="og:image" content={`/og/${post.slug}.png`} />
<meta name="twitter:image" content={`/og/${post.slug}.png`} />
</Head>
{/* Page content */}
</>
);
}Caching Strategies
Edge Caching (Recommended)
javascript
// app/api/og/route.js
export const runtime = 'edge';
export const revalidate = 86400; // 24 hours
export async function GET(request) {
// ... generate image
return new NextResponse(imageBuffer, {
headers: {
'Content-Type': 'image/png',
'Cache-Control': 'public, s-maxage=86400, stale-while-revalidate=604800'
}
});
}Incremental Static Regeneration
javascript
// app/blog/[slug]/opengraph-image.js
export const revalidate = 86400;
export default async function Image({ params }) {
// Generate image...
}Error Handling
javascript
export async function GET(request) {
try {
const response = await fetch('https://ogimageapi.io/api/generate', {
// ...
});
if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}
return new NextResponse(await response.arrayBuffer(), {
headers: { 'Content-Type': 'image/png' }
});
} catch (error) {
console.error('OG image generation failed:', error);
// Return fallback image
const fallbackImage = fs.readFileSync('./public/og-fallback.png');
return new NextResponse(fallbackImage, {
headers: { 'Content-Type': 'image/png' }
});
}
}Best Practices
- Generate at build time for static content
- Use edge caching for dynamic content
- Set appropriate cache headers
- Have fallback images for errors
- Validate input to prevent errors