Menu
Sign up

HTML to image with npm: which package actually renders what

html-to-image, html2canvas, dom-to-image, satori, @vercel/og, node-html-to-image, Puppeteer: how each renders HTML, where it runs and its CSS limits.

Published November 4, 2026 · 6 min read

Searching npm for "html to image" returns packages that do three completely different things. html2canvas, html-to-image and dom-to-image run in the browser and redraw a DOM node onto a canvas. Satori and @vercel/og are layout engines that turn JSX-style markup into SVG without any browser, so they run on the edge. Puppeteer, Playwright and node-html-to-image start real Chrome in Node and take a screenshot. Pick by where your code runs and how much CSS you need; the table below shows what each one actually renders.

Versions, release dates and weekly downloads were read from the npm registry and npm's download-count API on 4 November 2026 (downloads for 28 October – 3 November).

The comparison

Package How it renders Runs in CSS and limits Latest release Weekly downloads
html2canvas Re-implements painting on a canvas from computed styles Browser Unsupported list includes filter, mix-blend-mode, object-fit; cross-origin images need CORS 1.4.1, Jan 2022 25.5 M
html-to-image DOM clone in an SVG <foreignObject>, painted by the browser Browser Browser CSS; images and fonts are inlined, and an image blocked by CORS aborts the capture 1.11.13, Feb 2025 11.4 M
dom-to-image Same technique (html-to-image is its fork) Browser Same as above 2.6.0, Oct 2017 533 k
dom-to-image-more Maintained fork of dom-to-image Browser Same as above 3.11.0, Sep 2026 317 k
satori Own layout engine (Taffy) and text shaping → SVG Browser, Node, Web Workers Not complete CSS: no tables, no @font-face, no JS; flex, grid and block layout 0.63.2, Oct 2026 6.9 M
@vercel/og Satori 0.33.5 + resvg → PNG Node, edge (Vercel) Flexbox only, ttf/otf/woff fonts, 500 KB bundle (Vercel docs) 1.0.3, Sep 2026 2.9 M
node-html-to-image Puppeteer + puppeteer-cluster, Handlebars templates Node Real Chrome 6.2.0, Jul 2026 49 k
puppeteer Real Chrome (Chrome for Testing) Node Real Chrome 25.16.0, Oct 2026 15.5 M
playwright Real Chromium, Firefox or WebKit Node Real browser 1.64.1, Oct 2026 161 M

Download counts measure popularity, not fit: Playwright's figure is mostly its test runner, and html2canvas is an optional dependency of jsPDF (20.9 M downloads that week), so many installs never call it directly.

Browser packages: html2canvas, html-to-image, dom-to-image

None of these takes a real screenshot — a web page has no API for that. They rebuild the element:

  • html2canvas walks the DOM and draws each box with its own renderer; its feature page lists the CSS it does not support. It has not had a release since January 2022.
  • html-to-image and the dom-to-image family clone the node, inline styles, fonts and images as data URLs, wrap it in SVG and let the browser paint it. More CSS survives.

We tested html2canvas 1.4.1 and html-to-image 1.11.13 in Chromium. A cross-origin image without CORS headers was dropped by html2canvas without an exception and made html-to-image's toPng reject. With allowTaint: true, html2canvas drew the image but toDataURL then threw "Tainted canvases may not be exported". html2canvas ignored filter: invert(1), which html-to-image rendered. Neither drew the content of an <iframe>.

Use them for "save this chart or receipt as PNG" inside your app. They are a poor fit for images that must be identical for everyone, such as social cards: the result depends on the visitor's browser and installed fonts. Code for both is in the JavaScript guide.

Satori and @vercel/og: a layout engine, not a browser

Satori takes React-element-shaped markup, lays it out with its own engine and returns an SVG; you convert that to PNG with resvg or sharp. It needs your font files as data and runs in browsers, Node.js and Web Workers — and, through @vercel/og, in edge functions where Chrome cannot run. This ran on Node 22 with satori 0.63.2 and @resvg/resvg-js:

import satori from 'satori';
import { Resvg } from '@resvg/resvg-js';
import { readFile, writeFile } from 'node:fs/promises';

const font = await readFile('./DejaVuSans.ttf');   // TTF, OTF, WOFF or WOFF2 as a Buffer
const svg = await satori(
  {
    type: 'div',
    props: {
      style: {
        width: '100%', height: '100%', display: 'grid', gridTemplateColumns: '1fr 1fr',
        background: '#0f172a', color: '#f8fafc', fontSize: 56, padding: 80,
      },
      children: [
        { type: 'div', props: { children: 'Left' } },
        { type: 'div', props: { children: 'Right' } },
      ],
    },
  },
  { width: 1200, height: 630, fonts: [{ name: 'DejaVu Sans', data: font, weight: 400, style: 'normal' }] },
);
await writeFile('card.png', new Resvg(svg).render().asPng());

With a JSX transform you write the same tree as <div style={{…}}>. Satori's README is frank that it is "not a complete CSS implementation": display: table throws, @font-face and @keyframes are ignored, and no scripts run. Recent versions added grid and block layout — the grid above rendered as two columns.

@vercel/og (and next/og, which bundles it) wraps Satori and resvg in an ImageResponse. Version 1.0.3 depends on Satori 0.33.5, not the current release, and Vercel's documentation lists its limits: flexbox and a subset of CSS (display: grid "will not work"), ttf/otf/woff fonts only, and a 500 KB bundle including fonts and images. For a title on a gradient in Next.js it is free and fast; when you hit the limits, see Next.js OG images without the @vercel/og limits.

Puppeteer, Playwright and node-html-to-image: real Chrome

When the HTML must render exactly as in Chrome — your real stylesheet, web fonts, emoji — use a browser. Puppeteer's install downloads Chrome for Testing (about 282 MB on Linux, per its install guide); Playwright installs its browsers with npx playwright install. node-html-to-image is a convenience wrapper: it depends on puppeteer, puppeteer-cluster and handlebars, takes an html string plus content for the template, and screenshots body or a selector.

All three need a server that can run Chrome: memory per page, system libraries and fonts in the container, a timeout per render, and isolation when the HTML comes from users. They do not run in edge functions. A full Puppeteer example with setContent, deviceScaleFactor and element screenshots is in the JavaScript guide.

When an API is simpler than any package

An HTML to image API is Chrome's rendering behind one fetch, which matters in three cases: you need full CSS on the edge or serverless where Chrome cannot run; you need identical output for every visitor, which browser-side packages cannot promise; or you do not want to operate Chrome. With Shareglint you save HTML and CSS once as a template with fields (POST /v1/templates), then POST /v1/images?wait=true with the values returns the image URL in PNG, JPEG, WebP, PDF, GIF, MP4. No package to install — the request is plain HTTP, shown for Node in the JavaScript guide.

Be clear about what you give up compared with running Puppeteer: scripts are removed from templates, external URLs inside the HTML and CSS are dropped (images arrive through image fields), and fonts come from the installed list — no uploads. If your HTML is different on every request, a service that takes the whole document each time, such as HTMLCSStoImage, fits better (comparison). Free accounts get 100 images a month with a watermark; Starter is $9 a month for 2,000 (pricing).

Quick decision

  • User clicks "download as PNG" in your app: html-to-image; html2canvas if its output suits your CSS.
  • OG images in Next.js, simple layout: @vercel/og / next/og.
  • Edge function, your own layout, fonts you ship: satori + resvg.
  • Server, exact Chrome rendering, you run infrastructure: puppeteer or playwright (node-html-to-image if you like Handlebars).
  • Server or edge, exact rendering, no Chrome to run: an API.

Same question in other languages: Python, PHP, C#.

FAQ

What is the best npm package to convert HTML to an image?

It depends on where the code runs. In the browser, html-to-image (or html2canvas) captures a DOM node. On a server with full CSS support, Puppeteer or Playwright drive real Chrome. On the edge or in Next.js, Satori or @vercel/og render a CSS subset without a browser.

Is html-to-image better than html2canvas?

They work differently. html2canvas re-implements rendering in JavaScript and skips some CSS (its feature page lists filter, mix-blend-mode, object-fit and others). html-to-image wraps a DOM clone in an SVG foreignObject and lets the browser paint it, so more CSS survives, but a cross-origin image without CORS headers makes the whole capture fail.

Can Satori render any HTML and CSS?

No. Satori has its own layout engine and its README says it is not a complete CSS implementation: no tables, @font-face is ignored (you pass font files yourself), and no JavaScript runs. Standalone Satori 0.63 supports grid; @vercel/og 1.0.3 pins Satori 0.33.5, and Vercel documents it as flexbox only.

Which html to image npm package works in Node without Chrome?

Satori (with resvg or sharp to turn its SVG into PNG) and @vercel/og. Every package that gives full CSS support in Node, including node-html-to-image, runs Puppeteer and Chrome underneath.