IMAGE

HTML to Image: A Developer's Guide (2026)

How to convert HTML, CSS and DOM nodes to PNG/JPG images. Use cases, libraries, browser APIs, OG image generation, watermarks, and how to do it without uploading code to a server.

DuneTools · · 10 min read

Converting HTML to an image, taking arbitrary markup and rendering it as a static PNG or JPG, is one of those operations that sounds simple and turns out to be surprisingly nuanced. Web fonts, CORS, dynamic content, modern CSS features, transparent backgrounds, retina rendering… each adds a new wrinkle.

This guide covers what you need to know in 2026 to pick the right approach for your use case: from one-off screenshots to dynamic Open Graph image generation at scale.

Use cases, when you need HTML to Image

1. Dynamic Open Graph images

Every blog post, product page or user profile gets a unique social card. Instead of hand-designing each one in Figma, you build an HTML/CSS template and render to PNG at build time (or on demand). This is what Vercel’s @vercel/og, Cloudflare Workers, and DuneTools’ own per-page OG cards (see /og/ in this site) do under the hood.

2. Screenshots of components

Showing a charts library, a UI component, an admin dashboard, capturing a specific DOM node lets you embed a snapshot without complex screenshot tooling. Useful for documentation, marketing sites, status pages.

3. Email-friendly imagery

Email clients (Outlook in particular) have terrible HTML/CSS support. A dynamic invoice, a chart, a status badge, render it server-side to PNG and embed as <img> for guaranteed identical rendering everywhere.

4. Receipts, invoices, certificates

Static, non-editable PDF/PNG output of structured HTML data. Tools like Stripe receipts, certificate generators, ticket printers all do this.

5. Watermarking and signed visuals

Render dynamic text/code over a base image as a single composite PNG, for protection, attribution, or sharing.

6. Code-to-image (the modern alternative to screenshots)

Tools like Carbon, Ray.so, Polacode render syntax-highlighted code as beautiful, branded images. Underneath: HTML/CSS rendered to PNG.

Approaches and trade-offs

A) Server-side: Puppeteer / Playwright

Run a real Chromium instance on the server, navigate to a URL or set HTML content, screenshot.

Pros: pixel-perfect fidelity (it IS a real browser), full CSS/JS/font support, can handle any URL with cookies/auth.

Cons: heavy (200+ MB Chromium binary), slow to spin up (~1-3 seconds cold start), expensive on serverless. Cold starts on AWS Lambda are notoriously painful.

Best for: low-volume, high-fidelity needs (PDF reports, full-page screenshots, complex dynamic content).

import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setContent(htmlString);
const buffer = await page.screenshot({ type: 'png' });
await browser.close();

B) satori + resvg (Vercel’s approach)

Render JSX/HTML-ish markup to SVG with satori (a custom rendering engine), then rasterize to PNG with resvg. Used by @vercel/og, Next.js OG generation, and DuneTools’ build-og.mjs script.

Pros: very fast (~50-200 ms), tiny dependency (~10 MB), works in Edge runtimes (Cloudflare Workers, Vercel Edge Functions, AWS Lambda@Edge).

Cons: subset of CSS (no z-index, limited Grid, no JS execution). You design within the constraints, but they cover 90% of OG-image use cases.

Best for: high-volume OG image generation, Edge Functions, build-time rendering.

C) html2canvas / dom-to-image (client-side)

Pure JavaScript libraries that walk the DOM, serialize it to SVG foreignObject, and rasterize via HTML5 Canvas. Runs entirely in the user’s browser.

Pros: zero server, free, no CORS issues for same-origin content, instant feedback.

Cons: subtle rendering differences from real browser (some CSS features approximated, web font loading needs care), CORS-blocked external resources need proxy.

Best for: client-side capture of components users are looking at, screenshot-to-clipboard features, internal tools.

D) DuneTools HTML to Image

A combination of approach C with WebAssembly-accelerated rendering, designed for occasional one-off conversions. Paste HTML/CSS, get PNG/JPG back without setting up infrastructure or learning a library.

Pros: zero install, zero server, works offline after page load, handles modern CSS via the browser’s actual engine.

Cons: not for high-volume programmatic use (use B or A for that).

Best for: developers who need occasional HTML-to-image conversions without spinning up infrastructure.

Common pitfalls

Web font loading

If your HTML uses a Google Font, the renderer needs the font loaded before capturing. Solutions:

  • Inline the font as a base64 data URI in the CSS (works everywhere).
  • Wait for document.fonts.ready Promise before capture (client-side).
  • For server-side (Puppeteer): wait for networkidle0 before screenshot.

CORS-restricted images

Images from external domains tagged crossOrigin="anonymous" can be drawn to canvas. Without the tag (default), drawing throws “tainted canvas”, your output PNG can’t be exported.

Fix: ensure <img crossorigin="anonymous"> and the source server returns Access-Control-Allow-Origin. Your own assets always work.

Background colour

Transparent PNG output is great for some use cases (UI components, badges) but disastrous for others (the social preview comes out invisible against Twitter’s dark theme). Always explicitly set background unless transparency is desired.

Resolution / DPI

A 1×1 ratio capture of a 1200px-wide DOM node is 1200×Y, fine for normal screens but blurry on retina. Multiply by 2 (use scale: 2 in html2canvas, or 2×width in puppeteer) for crisp retina output.

Animation / dynamic content

Capture freezes whatever frame is current. If your DOM has loading spinners, lazy-loaded images, or CSS animations, they’ll be captured mid-animation. Solutions: wait for animations to settle (setTimeout after requestAnimationFrame), force final state via class, or pause animations programmatically before capture.

Privacy: when local matters

Server-based screenshot services (URL2PNG, Browserless, ScreenshotAPI, etc.) execute your HTML on their infrastructure. The HTML, the rendered image, even URL parameters can be logged.

For HTML containing sensitive UI (admin dashboards with real data, internal tools, customer info), this is a non-starter. The fix is client-side rendering: HTML stays in the user’s browser, gets rendered there, never leaves.

DuneTools HTML to Image does this, paste your HTML, get PNG, nothing logged anywhere.

Quick reference: which tool by need

NeedTool
One-off conversion, no infraDuneTools HTML to Image
Component screenshot in your own apphtml2canvas / dom-to-image
OG images at build timesatori + resvg
Dynamic OG images at request time on Edge@vercel/og
Server-side high-fidelity (full pages, PDFs)Puppeteer / Playwright
Programmatic batch conversionsatori (client-side) or Puppeteer (server)

Real-world examples

Static blog OG images: each post gets a unique 1200×630 PNG generated at build time using satori, with the post title, author and accent color.

SaaS receipt PDFs: render an HTML invoice template server-side with Puppeteer, save as PDF, attach to email.

Internal screenshot tool: developer dashboard has “Share state” button that captures the current page state via html2canvas and uploads to S3, for bug reports, staff can paste a precise screenshot of any view.

Code-to-image generator: code editor + syntax highlighter, when user clicks “Export”, run html2canvas on the rendered code block, download PNG.

Marketing automation: dynamic ad images for paid campaigns, instead of designing 200 variants in Figma, build one HTML template, parametrize via URL, render to PNG at request time.

Summary

HTML to image is a solved problem with multiple good tools, pick by use case:

  • Need a one-off PNG of arbitrary HTML? DuneTools HTML to Image, zero infra, instant.
  • Building an app that needs to capture user content? html2canvas client-side.
  • Generating OG images at scale? satori + resvg, build-time or Edge.
  • Need pixel-perfect server fidelity? Puppeteer.

Privacy matters: for sensitive HTML (admin UIs, dashboards, internal tools), always pick a client-side option that doesn’t transmit your markup.