Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Direct answer: choose the rendering location before choosing a package. For an element that already exists in a browser, html-to-image can export the DOM node to PNG, JPEG, SVG, Blob, canvas, or pixel data. For server-side HTML templates, node-html-to-image runs Puppeteer in headless Chromium. For navigation, full-page capture, and browser automation, use Playwright or Puppeteer directly. If you do not want to operate a browser runtime, ScreenshotNeo provides a one-request screenshot API and an MCP server for AI agents.

Choose the rendering model first

These approaches solve different problems. A browser-side library starts with a DOM node that your application has already rendered. A Node.js package or browser automation framework creates a browser page, inserts HTML or navigates to a URL, waits for the desired state, and captures it. An external screenshot API performs that browser work for you.

Use case Best starting point What is captured Main consideration
Export one element in an existing web app html-to-image A DOM node and its subtree Fonts, images, cross-origin content, and data-URI limits
Render supplied HTML on a Node.js server node-html-to-image Template output or a selected element Chromium/Puppeteer deployment and deterministic waiting
Navigate to pages or automate a browser Playwright or Puppeteer Viewport, full page, or selected element Viewport, device scale, readiness, and browser lifecycle
Generate screenshots without managing Chromium ScreenshotNeo URL, HTML, element, or PDF according to request options API key and request configuration

Documentation describes APIs rather than a controlled speed or fidelity benchmark, so test your own HTML, asset mix, and deployment before making a performance claim.

Browser-side conversion with html-to-image

html-to-image clones the selected subtree, copies computed styles, reconstructs pseudo-elements, embeds fonts and images, serializes the clone as SVG using foreignObject, and can rasterize that SVG through an off-screen canvas. Its promise-based functions include toPng, toJpeg, toBlob, toSvg, toCanvas, and toPixelData.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Install and export a PNG

npm install html-to-image
import { toPng } from 'html-to-image';

const node = document.querySelector('#invoice');
if (!(node instanceof HTMLElement)) {
  throw new Error('Expected #invoice to be an HTMLElement');
}

const dataUrl = await toPng(node, {
  cacheBust: true,
  pixelRatio: 2
});

const link = document.createElement('a');
link.download = 'invoice.png';
link.href = dataUrl;
link.click();

pixelRatio increases output pixels relative to CSS pixels. Confirm the target browser and output dimensions before using a high value, because a large subtree can exceed browser memory or data-URL limits.

Other output types

import { toJpeg, toBlob, toSvg, toCanvas, toPixelData } from 'html-to-image';

const node = document.querySelector('#card') as HTMLElement;
const jpegUrl = await toJpeg(node, { quality: 0.92 });
const blob = await toBlob(node);
const svgUrl = await toSvg(node);
const canvas = await toCanvas(node);
const pixels = await toPixelData(node);

// Example: upload the Blob
if (blob) {
  await fetch('/uploads/card', { method: 'POST', body: blob });
}

Make browser-side output reliable

  • Wait until web fonts have loaded: await document.fonts.ready.
  • Wait for images and ensure they have usable dimensions before calling the library.
  • Use same-origin assets or configure image servers for cross-origin use. A canvas tainted by cross-origin content cannot be read successfully.
  • Keep the exported subtree manageable. The project warns that large DOMs can fail because data-URI limits vary by browser.
  • Use a current Chrome, Firefox, or Safari; the project documentation says Internet Explorer is not supported.

The project documentation reports that Chrome performed significantly better for large DOM trees in its tested context. Treat that as a project-specific qualitative observation, not a universal benchmark.

Server-side HTML with node-html-to-image

node-html-to-image uses Puppeteer in headless mode and documents TypeScript support. It can render a template to PNG or JPEG, write a file, return binary or base64 data, target a selector, and run hooks before setting HTML or before taking the screenshot.

Install and render a TypeScript template

npm install node-html-to-image
npm install -D typescript tsx @types/node
import nodeHtmlToImage from 'node-html-to-image';

const result = await nodeHtmlToImage({
  output: './dist/card.png',
  html: `
    <html>
      <head>
        <style>
          * { box-sizing: border-box; }
          body { margin: 0; width: 1200px; font-family: Arial, sans-serif; }
          .card { width: 1200px; padding: 64px; background: #101827; color: white; }
          h1 { margin: 0 0 16px; font-size: 54px; }
        </style>
      </head>
      <body>
        <section class="card">
          <h1>{{title}}</h1>
          <p>{{subtitle}}</p>
        </section>
      </body>
    </html>`,
  content: {
    title: 'TypeScript rendering',
    subtitle: 'Generated on the server'
  },
  selector: '.card',
  type: 'png',
  waitUntil: 'networkidle0'
});

console.log(result);

Set dimensions in CSS, commonly on body or the selected element. Use a pre-screenshot hook when application data, fonts, or images need to be prepared. A deterministic job should define the viewport, wait condition, and timeout rather than relying on an immediate capture.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #2
TypeScript Programming Language - Software Engineer & Coder T-Shirt
  • TypeScript implements a superset of syntax for strictly typed development, facilitating deep static analysis and enhanced development environment integration. The compiler translates source into standard script formats, ensuring parity across any runtime.
  • TypeScript is ideal for front-end developers, full-stack engineers, and software architects who build large-scale web applications. It serves those looking to improve code excellence, reduce bugs through static checking, and maintain complex projects more.
  • Lightweight, Classic fit, Double-needle sleeve and bottom hem

When this route is a good fit

  • Use it for invoices, social cards, reports, and other HTML templates generated by a Node.js service.
  • Plan for the Chromium runtime in local, container, and serverless deployments.
  • Make external fonts and images reachable from the rendering environment, or embed them.
  • Reuse a browser process for batches when appropriate, while isolating pages and closing resources on shutdown.

Direct browser automation with Playwright

Playwright is appropriate when you need navigation, interactions, custom viewports, or full-page capture. Its Page API supports a TypeScript-compatible screenshot flow, output paths, image quality, and CSS-pixel or device-pixel scaling.

npm install playwright
npx playwright install chromium
import { chromium } from 'playwright';

const browser = await chromium.launch();
const page = await browser.newPage({
  viewport: { width: 1440, height: 900 },
  deviceScaleFactor: 2
});

try {
  await page.goto('https://example.com', { waitUntil: 'networkidle' });
  await page.evaluate(() => document.fonts.ready);
  await page.screenshot({
    path: 'page.png',
    fullPage: true,
    type: 'png'
  });
} finally {
  await browser.close();
}

Use a selector when only one component is needed:

const chart = page.locator('#chart');
await chart.screenshot({ path: 'chart.png' });

Playwright’s scale setting determines whether output follows CSS pixels or device pixels. Choose explicitly so a retina screenshot is not confused with a larger layout viewport. Wait for an application-specific selector when network idle is not sufficient.

Direct browser automation with Puppeteer

Puppeteer’s Page.screenshot() returns a base64 string or Uint8Array, depending on the overload. It is useful when your project already uses Puppeteer or needs its browser and page APIs.

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.setViewport({ width: 1200, height: 800, deviceScaleFactor: 2 });
  await page.goto('https://example.com', { waitUntil: 'networkidle0' });
  await page.evaluate(() => document.fonts.ready);
  const bytes = await page.screenshot({ type: 'png', fullPage: true });
  await Bun.write('page.png', bytes);
} finally {
  await browser.close();
}

In a Node.js project that does not provide Bun.write, write the returned bytes with your runtime’s filesystem API, for example writeFile from Node’s fs/promises.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Rendering checklist for consistent images

  1. Fix the viewport: set width, height, and device scale before loading content.
  2. Define readiness: wait for a selector, a known application state, fonts, and images.
  3. Control dimensions: set CSS width and height for cards; use full-page capture only when the entire document is intended.
  4. Control assets: verify image URLs, CORS headers, authentication, and font loading from the rendering environment.
  5. Choose format: PNG preserves sharp text and transparency; JPEG is smaller for photographic content but has quality loss; SVG is useful when the consumer accepts vector output.
  6. Clean up: close pages and browsers, and limit concurrency so rendering jobs do not exhaust memory.

Common failures and fixes

Blank or partially rendered output

The capture ran before data, fonts, or images finished. Add a selector-based readiness condition, wait for document.fonts.ready, and check image completion in the page before taking the screenshot.

Images or fonts are missing

The browser cannot reach the asset, the URL requires authentication, or cross-origin policy blocks it. Serve assets to the rendering environment, provide the required credentials, embed critical assets, and inspect network failures.

“Canvas is tainted” or export throws a security error

This usually indicates cross-origin content. Configure the image server for cross-origin use and load it with the appropriate CORS behavior, or move the asset to the same origin. Browser-side exports remain subject to canvas security rules.

Large DOM fails or produces an oversized data URL

Reduce the subtree, lower the pixel ratio, split a long document into sections, or use a headless-browser screenshot that writes binary output instead of a data URL. Browser data-URI limits differ.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Chromium cannot launch in production

Install the browser required by Playwright or Puppeteer, include system dependencies in the container, and verify executable permissions. Keep the browser version aligned with the package and test the exact deployment image.

Output dimensions are surprising

CSS pixels, device pixels, body dimensions, and full-page layout are separate concepts. Log the viewport and element bounding box, then set the intended width, height, and scale explicitly.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

ScreenshotNeo accepts one GET request and returns a PNG, JPEG, WebP, or PDF. It can capture a URL or supplied HTML and includes options for full-page output with lazy images, CSS-selector element capture, dark mode, 12 device presets or any viewport, retina scale, PDF paper and page ranges, custom CSS and JavaScript, pre-capture clicks, hidden selectors, selector/delay/network-idle waits, blocked ads or resource types, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture of 100 URLs per call, usage reporting, and an OpenAPI specification. Parameters used by other screenshot APIs also work.

Before capture, it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the page verdict and whether it was billed. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

See the ScreenshotNeo documentation for request options. A minimal call is:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python:

import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());

The Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; yearly billing provides two months free, and every feature is included on every plan. Create a free ScreenshotNeo account to get started.

Cost, performance, and reliability decisions

Browser-side conversion avoids a server browser but consumes the user’s memory and is constrained by browser security and data-URL limits. Headless rendering centralizes output and supports navigation, but Chromium startup, memory, and concurrency become operational costs. Reuse a controlled browser process for batches, isolate pages, cap concurrent jobs, and record failures with the URL, viewport, wait condition, and browser version. ScreenshotNeo shifts browser operations to an API: choose its cache TTL for repeat captures, use asynchronous jobs for long work, and use bulk requests for up to 100 URLs.

FAQ

Can TypeScript itself render HTML?

TypeScript supplies types and compiles to JavaScript; a browser renderer, DOM-to-image library, or headless browser performs the actual rendering.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Should I use PNG or JPEG?

Use PNG for text, interfaces, and transparency. Use JPEG when photographic content and a smaller lossy file are more important.

Can I capture an HTML string without a URL?

Yes. Set page content in Playwright or Puppeteer, or pass a template to node-html-to-image. A browser-side library instead requires an existing DOM node.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.