Dev.to WebDev 🛠 Dev 👁 0 📖 2 min read

I got tired of $20/mo screenshot APIs, so I built a free one on top of the browser I already had to run anyway

Every "HTML to image" API I priced out wanted $15–25/month before I'd rendered a single image. What they're actually doing under the hood is running headless Chrome and taking a screenshot — which is real infrastructure,

I got tired of $20/mo screenshot APIs, so I built a free one on top of the browser I already had to run anyway

Every "HTML to image" API I priced out wanted $15–25/month before I'd rendered a single image. What they're actually doing under the hood is running headless Chrome and taking a screenshot — which is real infrastructure, but not $20/mo-per-seat real. So I built the free version I wanted to exist, and it's live now as part of a small suite of tools I run under WebsiteGeek.

What it actually does
You paste HTML in one box, CSS in another, and get back a real image file — not a canvas hack, not a client-side screenshot of your own browser window. It's server-side Puppeteer against @sparticuz/chromium, so what you get is what any visitor's browser would actually render, fonts and gradients included.

Free, no sign-in: PNG, up to 800×800px, 10 renders/day
Paid tiers add: JPEG/WebP/AVIF/SVG-wrapped export, retina scale (1x–3x), sizes up to 4000×4000px, full-page and element-selector capture, transparent backgrounds
Six built-in size presets — Instagram Post/Story, Facebook/OG share image, X card, YouTube thumbnail, 16:9 slide — plus a custom size box
The part I actually enjoyed building
The boring-sounding problem turned out to be the interesting one: Vercel serverless functions cold-start, and launching headless Chromium from scratch is the expensive part of that cold start — multiple seconds before the page even begins loading. Most "screenshot API" tutorials just eat that cost on every request.

Instead the render endpoint now caches one launched browser across warm invocations of the same function instance, instead of relaunching Chromium per request. On a warm instance that turns a multi-second cold start into effectively nothing. I also added a small priority queue in front of it — when a warm instance is already busy rendering, a paid-tier request gets served ahead of a free one instead of everyone just waiting in arrival order, but nobody waits at all unless there's real contention.

The failure mode I hadn't expected: a page with one slow external image used to fail the whole render once the timeout hit. Now it degrades instead — ships whatever rendered in time and flags the result, rather than throwing away a mostly-good image over one slow asset.

Where I'd actually use this
Open Graph/share images generated from real data instead of a static template, quick social crops without opening a design tool, or turning an HTML invoice/certificate template into a downloadable image on the fly. Anywhere you'd reach for a "screenshot this webpage" API and balk at the price.

It's free to try with no account: websitegeek.net/html-to-image. I'd genuinely like to hear what breaks it — that's usually the fastest way I find the next thing worth fixing.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.