How to Generate Dynamic OG Images with an API

Every link shared on Slack, X, or LinkedIn pulls an OG image from the target URL. If that image is missing or static, engagement drops. If it is dynamic (title, description, or branding that changes per page), it requires a rendering service.
The standard solution is @vercel/og, which works great if you deploy on Vercel. For Next.js on Netlify, Railway, or bare VMs, and for Astro or Nuxt projects, you need a different approach. This tutorial shows how to generate dynamic OG images with the Rendex API across all three frameworks.
Prerequisites
- A Rendex API key (free tier, 100 calls/month). Get one here.
- Node.js 18+ or Bun (for the JS SDK). Python 3.9+ if using the Python SDK.
- An existing Next.js, Astro, or Nuxt project to add OG images to.
How It Works
The Rendex API accepts raw HTML in the request body and renders it to a PNG. You define an HTML template for your OG image (title, author, branding), pass it to the API with a 1200×630px viewport, and receive back a PNG ready to serve as an og:image.
No headless browser to manage. No Vercel lock-in. The rendered image comes back as binary data you can stream directly to the client or store in a CDN.
import { Rendex } from "@copperline/rendex"
// npm install @copperline/rendex
// Get your API key at https://rendex.dev/login
const rendex = new Rendex("rdx_your_key")
// Generate an OG image from raw HTML
const { image } = await rendex.screenshot({
html: `<!DOCTYPE html>
<html>
<body style="margin:0;background:#0f172a;display:flex;align-items:center;
justify-content:center;width:1200px;height:630px;
font-family:sans-serif;color:white">
<div style="padding:80px;text-align:left">
<p style="color:#94a3b8;font-size:18px;margin:0 0 12px">rendex.dev</p>
<h1 style="font-size:64px;font-weight:700;margin:0 0 20px;line-height:1.1">
How to generate OG images with an API
</h1>
<p style="color:#64748b;font-size:22px;margin:0">5 min read</p>
</div>
</body>
</html>`,
width: 1200,
height: 630,
format: "png",
})
// image is Uint8Array — write to disk or stream as a response
// Uint8ArrayToBuffer: Buffer.from(image)The html parameter accepts any valid HTML including inline CSS, web fonts via @import, and CSS Grid or Flexbox layouts. For the full parameter reference see the API reference.
Step 1: Next.js (App Router)
In Next.js App Router, create a Route Handler that accepts a page title as a query parameter and returns a PNG:
// app/og/route.ts
import { Rendex } from "@copperline/rendex"
const rendex = new Rendex(process.env.RENDEX_API_KEY!)
// Add RENDEX_API_KEY to your .env.local
// Get your key at https://rendex.dev/login
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const title = searchParams.get("title") ?? "My Site"
const description = searchParams.get("description") ?? ""
const { image } = await rendex.screenshot({
html: `<!DOCTYPE html>
<html>
<body style="margin:0;background:#0f172a;width:1200px;height:630px;
display:flex;align-items:center;font-family:system-ui,sans-serif;color:white">
<div style="padding:80px">
<p style="color:#94a3b8;font-size:18px;margin:0 0 16px">My Site</p>
<h1 style="font-size:56px;font-weight:700;margin:0 0 20px;line-height:1.15">
${title.replace(/</g, "<")}
</h1>
<p style="color:#64748b;font-size:22px;margin:0">
${description.replace(/</g, "<")}
</p>
</div>
</body>
</html>`,
width: 1200,
height: 630,
format: "png",
})
return new Response(image, {
headers: {
"Content-Type": "image/png",
"Cache-Control": "public, max-age=86400, immutable",
},
})
}Reference this endpoint from your page metadata:
// app/blog/[slug]/page.tsx
import type { Metadata } from "next"
export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {
const post = await getPost(params.slug)
return {
openGraph: {
images: [
{
url: `/og?title=${encodeURIComponent(post.title)}&description=${encodeURIComponent(post.excerpt)}`,
width: 1200,
height: 630,
},
],
},
}
}Step 2: Astro
In Astro, create an API endpoint at src/pages/og/[...slug].png.ts:
// src/pages/og/[...slug].png.ts
import type { APIRoute } from "astro"
import { Rendex } from "@copperline/rendex"
const rendex = new Rendex(import.meta.env.RENDEX_API_KEY)
// Add RENDEX_API_KEY to your .env
// Get your key at https://rendex.dev/login
export const GET: APIRoute = async ({ params, request }) => {
const url = new URL(request.url)
const title = url.searchParams.get("title") ?? "My Site"
const { image } = await rendex.screenshot({
html: `<!DOCTYPE html>
<html>
<body style="margin:0;background:#0f172a;width:1200px;height:630px;
display:flex;align-items:center;font-family:sans-serif;color:white">
<div style="padding:80px">
<h1 style="font-size:56px;font-weight:700;margin:0;line-height:1.15">
${title.replace(/</g, "<")}
</h1>
</div>
</body>
</html>`,
width: 1200,
height: 630,
format: "png",
})
return new Response(image, {
headers: {
"Content-Type": "image/png",
"Cache-Control": "public, max-age=86400",
},
})
}Step 3: Nuxt
In Nuxt, add a server route at server/routes/og.get.ts:
// server/routes/og.get.ts
import { Rendex } from "@copperline/rendex"
export default defineEventHandler(async (event) => {
const query = getQuery(event)
const title = String(query.title ?? "My Site")
// RENDEX_API_KEY in your .env file
// Get your key at https://rendex.dev/login
const rendex = new Rendex(process.env.RENDEX_API_KEY!)
const { image } = await rendex.screenshot({
html: `<!DOCTYPE html>
<html>
<body style="margin:0;background:#0f172a;width:1200px;height:630px;
display:flex;align-items:center;font-family:sans-serif;color:white">
<div style="padding:80px">
<h1 style="font-size:56px;font-weight:700;margin:0;line-height:1.15">
${title.replace(/</g, "<")}
</h1>
</div>
</body>
</html>`,
width: 1200,
height: 630,
format: "png",
})
setResponseHeader(event, "Content-Type", "image/png")
setResponseHeader(event, "Cache-Control", "public, max-age=86400")
return image
})Quick Test with cURL
Before wiring any framework, confirm the Rendex API works with a direct call:
curl -X POST https://api.rendex.dev/v1/screenshot \
-H "Authorization: Bearer rdx_your_key" \
-H "Content-Type: application/json" \
-d '{
"html": "<body style=\"margin:0;background:#0f172a;width:1200px;height:630px;display:flex;align-items:center\"><h1 style=\"color:white;padding:80px;font-size:64px;font-family:sans-serif\">Hello OG</h1></body>",
"width": 1200,
"height": 630,
"format": "png"
}' --output og-test.png
open og-test.png # macOS — should show a 1200x630 dark imageTroubleshooting
Image is blank or all black: The body element needs an explicit width and height in the HTML itself when using flex layout. Set width: 1200px; height: 630px on the body style.
Text is clipped: The viewport dimensions in your API call (width: 1200, height: 630) must match the HTML body dimensions exactly. A mismatch causes Chromium to crop the output.
Fonts not rendering correctly: System fonts (sans-serif, serif) render as the Chromium default, which varies. For consistent cross-platform rendering, embed a web font via @import url(...) in the HTML <style> block.
HTML entities in dynamic content: Always escape user content before injecting it into the HTML template. At minimum, replace < with < and & with & to prevent template injection.
Next Steps
The same approach works for social cards with custom branding, email header images, and preview thumbnails for any content type. See the OG image generation use case for more patterns.
For a no-code test, try the OG image preview toolto inspect how a URL's existing OG image renders across different platforms, or the HTML-to-image tool to render any HTML template to a PNG in your browser.
Ready to generate OG images in production? Get a free API key (100 calls/month, no credit card) and have the first image rendering in under five minutes.
Continue Reading
Jul 28, 2026
7 Ways to Generate OG Images (APIs, Libraries, Services)
May 12, 2026
Generating Social Cards in Next.js Without Vercel Lock-in
Sep 8, 2026
Extract Clean Article Text from Any URL
Solution
Dynamic OG Image Generator API — Open Graph Images at Scale
Solution
Hosted OG Image API — Signed Render URLs for Social Cards
Documentation
Api Reference
Free Tool
Html To Image Tool
Free Tool
Og Preview Tool
Comparison
Best Html To Image Apis 2026