HTML to Image API — How It Works

You call an API, send some HTML, and get back a pixel-perfect image in milliseconds. Simple from the outside — but what actually happens between your POST request and the PNG that lands in the response? Understanding the internals helps you write better templates, pick the right output format, and avoid the edge cases that catch developers off guard.

The request pipeline

Every render request passes through four stages:

Your appPOST /v1/render
Queue & routerLoad balancing
Chromium workerPre-warmed instance
Image pipelinePNG / JPEG / WebP

The key detail is step three: the Chromium worker is already running before your request arrives. There is no cold start, no browser launch penalty, no 2-second wait for a process to spin up. A pool of headless Chromium instances sits idle, each one ready to accept a new page immediately.

Pre-warmed Chromium instances

Starting a headless browser from scratch takes 800 ms to 2 s depending on the host. For a synchronous API that returns an image, that latency is unacceptable. RenderPix maintains a pool of pre-warmed Chromium instances that are kept alive between requests.

When a request arrives, the router picks an available worker, loads your HTML into a new tab (not a new process), waits for the page to reach a fully painted state, takes a screenshot of the viewport, then closes the tab. The Chromium process itself stays running, ready for the next request within a few milliseconds.

RenderPix uses Chromium, not Chrome. The open-source build strips features like Widevine DRM and PDF viewer that add binary weight without contributing to rendering fidelity. For HTML and CSS, there is no practical difference.

How your HTML is loaded

Your HTML string is loaded via page.setContent(), not via a URL navigation. This means:

The worker waits for networkidle0 (no pending network requests for 500 ms) or a hard timeout of 10 seconds, whichever comes first. If your template loads a web font and a background image, both must finish loading before the screenshot is taken — otherwise you get fallback fonts and broken images.

The image pipeline

After Chromium takes the screenshot, the raw pixel buffer passes through sharp, a high-performance Node.js image processing library. Sharp handles:

The encoded image bytes are streamed directly to the HTTP response. There is no temporary storage, no S3 upload step, no intermediate URL — just bytes in, bytes out.

Making your first request

The API accepts a JSON body with three required fields: html, width, and height. Format defaults to PNG if omitted.

bash — curl
curl https://renderpix.dev/v1/render \
  -X POST \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: rpx_your_key_here" \
  -d '{
    "html": "<!DOCTYPE html><html><body style=\"background:#18181b;display:flex;align-items:center;justify-content:center;height:100vh;margin:0\"><h1 style=\"color:#22d3ee;font-family:sans-serif\">Hello RenderPix</h1></body></html>",
    "width": 800,
    "height": 400,
    "format": "png"
  }' \
  --output hello.png

The same request in Node.js:

js — Node.js
import { writeFile } from 'node:fs/promises';

const html = `<!DOCTYPE html>
<html>
<head><meta charset="utf-8"></head>
<body style="
  margin: 0;
  width: 800px;
  height: 400px;
  background: #18181b;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: sans-serif;
">
  <h1 style="color: #22d3ee;">Hello RenderPix</h1>
</body>
</html>`;

const res = await fetch('https://renderpix.dev/v1/render', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-Api-Key': process.env.RENDERPIX_API_KEY,
  },
  body: JSON.stringify({
    html,
    width: 800,
    height: 400,
    format: 'png',
  }),
});

if (!res.ok) {
  throw new Error(`Render failed: ${res.status}`);
}

await writeFile('hello.png', Buffer.from(await res.arrayBuffer()));
console.log('Saved hello.png');

Choosing an output format

The right format depends on your content and use case:

js — format options
// PNG — lossless, largest file
{ html, width: 1200, height: 630, format: 'png' }

// JPEG — lossy, smaller file
{ html, width: 1200, height: 630, format: 'jpeg', quality: 85 }

// WebP — best compression, wide support
{ html, width: 1200, height: 630, format: 'webp', quality: 90 }

// Retina — 2× pixel density, same CSS dimensions
{ html, width: 1200, height: 630, format: 'png', deviceScaleFactor: 2 }

What can slow down a render

Typical render time is 80–200 ms. These factors push it toward the higher end:

For the fastest renders, keep your HTML self-contained: inline all CSS in a <style> block, base64-encode any fonts or small images, and avoid external HTTP dependencies in the template.

Error responses

The API returns standard HTTP status codes. A 400 means your request payload is invalid (missing html, unrecognized format, etc.). A 401 means the API key is missing or wrong. A 500 means the render itself failed — most often because Chromium hit the 10-second timeout waiting for external resources. Always check res.ok before consuming the response body as an image.

Interested in Python? See HTML to image in Python.

Ready to render your first image?

Sign up for a free account, grab your API key, and make your first render in under a minute.

← All articles API Reference →