Generate images at build time, for the whole collection at once.
Astro builds statically by default, so images are created once during the build. The first run costs one render per post; every rebuild after that gets the finished URLs back from cache for free.
src/pages/blog/[...slug].astro
---
import { getCollection } from 'astro:content';
import Layout from '../../layouts/Base.astro';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map((post) => ({ params: { slug: post.slug }, props: { post } }));
}
const { post } = Astro.props;
const res = await fetch('https://imageapi.dev/v1/images?wait=true', {
method: 'POST',
headers: {
Authorization: `Bearer ${import.meta.env.IMAGE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
template: 'blog-og',
modifications: {
title: post.data.title,
author: post.data.author,
date: post.data.pubDate.toISOString(),
site: 'yourblog.com',
},
}),
});
const { url: ogImage } = await res.json();
---
<Layout title={post.data.title} ogImage={ogImage}>
<slot />
</Layout>
A large collection fires many requests at once. If you see a 503 `sync_overloaded`, drop `?wait=true` and collect the URLs by ID afterwards — or simply retry, because the job is never lost.
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.