Converting HTML to an image is one of the most useful rendering operations in modern development. Social cards, invoices, OG images, email previews, and certificates — all start as HTML templates and need to become PNGs or JPEGs.
This guide covers three approaches: a dedicated rendering API (fastest to ship), Puppeteer (the DIY standard), and a serverless function with Satori (for React-specific templates). Each includes working code you can copy and run.
Method 1: Rendex API (Fastest)
The Rendex HTML-to-image API handles browser rendering on Cloudflare's edge network. Pass HTML as a string, get back a PNG, JPEG, WebP, or PDF. No Chromium install, no infrastructure.
# pip install rendex
from rendex import Rendex
from pathlib import Path
rendex = Rendex("YOUR_API_KEY")
html = """
<div style="width:1200px;height:630px;display:flex;align-items:center;
justify-content:center;background:linear-gradient(135deg,#667eea,#764ba2);
font-family:system-ui;color:white;font-size:48px;font-weight:bold;">
Hello from Rendex
</div>
"""
result = rendex.screenshot(html=html, format="png", width=1200, height=630)
Path("card.png").write_bytes(result.image)
print(f"Generated {result.metadata.bytes_size} bytes")
The equivalent JavaScript SDK call:
import Rendex from "@copperline/rendex";
import { writeFileSync } from "fs";
const rendex = new Rendex("YOUR_API_KEY");
const html = `
<div style="width:1200px;height:630px;display:flex;align-items:center;
justify-content:center;background:linear-gradient(135deg,#667eea,#764ba2);
font-family:system-ui;color:white;font-size:48px;font-weight:bold;">
Hello from Rendex
</div>
`;
const result = await rendex.screenshot({ html, format: "png", width: 1200, height: 630 });
writeFileSync("card.png", Buffer.from(result.image));
Or with raw HTTP — no SDK needed:
curl -X POST https://api.rendex.dev/v1/screenshot \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"html": "<div style=\"width:1200px;height:630px;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#667eea,#764ba2);font-family:system-ui;color:white;font-size:48px;font-weight:bold;\">Hello from Rendex</div>",
"format": "png",
"width": 1200,
"height": 630
}' --output card.png
When to use: Production workloads, batch generation, and any case where you don't want to manage browser infrastructure. 500 free calls/month to start. Get an API key.
Method 2: Puppeteer (Self-Hosted)
Puppeteer gives you a headless Chromium instance you control. You can render any HTML by loading it as a data URL or setting page content directly.
import puppeteer from "puppeteer";
import { writeFileSync } from "fs";
const html = `
<div style="width:1200px;height:630px;display:flex;align-items:center;
justify-content:center;background:linear-gradient(135deg,#667eea,#764ba2);
font-family:system-ui;color:white;font-size:48px;font-weight:bold;">
Hello from Puppeteer
</div>
`;
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport({ width: 1200, height: 630 });
await page.setContent(html, { waitUntil: "networkidle0" });
const screenshot = await page.screenshot({ type: "png" });
writeFileSync("card.png", screenshot);
await browser.close();
When to use: You need full browser control, want to run on your own infrastructure, or have specialized rendering requirements (e.g., browser extensions, network interception).
Trade-offs: Requires Chromium (~400 MB), significant memory per instance, cold starts in serverless, and you manage the infrastructure. At scale, browser management becomes a full-time ops concern.
Method 3: Satori + Resvg (React to SVG to PNG)
Vercel's Satori converts React JSX to SVG, and resvg converts SVG to PNG. This approach avoids a browser entirely but only supports a subset of CSS (Flexbox only, no Grid).
import satori from "satori";
import { Resvg } from "@resvg/resvg-js";
import { readFileSync, writeFileSync } from "fs";
const font = readFileSync("./Inter-Bold.ttf");
const svg = await satori(
{
type: "div",
props: {
style: {
width: 1200,
height: 630,
display: "flex",
alignItems: "center",
justifyContent: "center",
background: "linear-gradient(135deg, #667eea, #764ba2)",
fontFamily: "Inter",
color: "white",
fontSize: 48,
fontWeight: "bold",
},
children: "Hello from Satori",
},
},
{
width: 1200,
height: 630,
fonts: [{ name: "Inter", data: font, weight: 700, style: "normal" }],
}
);
const resvg = new Resvg(svg);
const png = resvg.render().asPng();
writeFileSync("card.png", png);
When to use: You're already in a React/Next.js ecosystem, need zero external dependencies, and your templates use only Flexbox layouts.
Trade-offs: No CSS Grid, limited CSS property support, no JavaScript execution, no web font loading (fonts must be bundled), and complex layouts may not render correctly. Great for simple cards, but not a general-purpose renderer.
Which Method Should You Choose?
| Method | Setup | CSS Support | Best For |
|---|---|---|---|
| Rendex API | 30 seconds | Full (Chromium) | Production, batch, AI agents |
| Puppeteer | 10 minutes | Full (Chromium) | Self-hosted, full browser control |
| Satori + Resvg | 5 minutes | Flexbox only | Simple cards, React ecosystem |
For most production use cases, an API is the right choice. You get full CSS support, zero infrastructure, and the ability to batch hundreds of renders in a single request.
See the HTML to Image use case page for more examples, or try the free tool to render HTML without writing code.
Common Use Cases for HTML-to-Image
- OG images — Generate unique social preview images for every blog post, product page, or user profile
- Social cards — Branded Twitter/LinkedIn cards from HTML templates
- Invoices — HTML-to-PDF invoice generation with dynamic data
- Certificates — Course completion certificates with custom names and dates
- Email previews — Render HTML emails as images for testing and review
Ready to start? Get a free API key (500 calls/month, no credit card) or read the API reference.
Want to see how Rendex compares to other rendering APIs? See the 2026 comparison.
Top comments (0)