Generate the image at build time and hand it to the metadata API.
Next.js resolves metadata on the server, so you can generate the image once at build time and never pay for it again. Our cache means even a rebuild of the same post costs nothing against your quota.
app/blog/[slug]/page.tsx
import type { Metadata } from 'next';
async function ogImage(title: string, author: string) {
const res = await fetch('https://imageapi.dev/v1/images?wait=true', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.IMAGE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
template: 'blog-og',
modifications: { title, author, site: 'yourblog.com' },
}),
// Same post, same image - so the cache can live forever.
next: { revalidate: false },
});
const { url } = await res.json();
return url as string;
}
export async function generateMetadata({ params }): Promise<Metadata> {
const post = await getPost(params.slug);
return {
title: post.title,
openGraph: { images: [await ogImage(post.title, post.author)] },
twitter: { card: 'summary_large_image' },
};
}
Never call the API from a client component — the key would leak into the browser. `generateMetadata` runs on the server only, and that is exactly where this belongs.
With ?wait=true the finished image arrives on the same connection — usually in about a second.
{
"id": "img_01m08s3ez1zcv28h335g9923bw",
"status": "completed",
"url": "https://cdn.imageapi.dev/renders/img_01m08s3ez1zcv28h335g9923bw.png",
"duration_ms": 912
}
A hundred images a month free, then $9 for two thousand. The same post rendered twice returns the existing URL and uses no quota — so rebuilding your site costs nothing.