ScreenshotNeo

BlogComparisons

Node.js Alternatives to html2canvas for HTML Screenshots

html2canvas is browser-only. Compare Playwright and Puppeteer for Node.js screenshots, with runnable code, fixes, and a hosted API option.

By the ScreenshotNeo team1 October 20263 min read

Short answer: html2canvas is browser-only DOM reconstruction. For server-side Node.js screenshots, use Playwright or Puppeteer, which drive a real headless browser.

1. Why html2canvas fails in Node.js

Node has no window, document, layout engine, or canvas DOM. html2canvas walks the DOM and recreates supported styles; it does not capture rendered pixels. Its FAQ recommends Playwright or Puppeteer for server-side work. Cross-origin images and iframes remain constrained by browser security. See the documentation and FAQ.

2. Playwright

Playwright supports viewport, element, and full-page screenshots and multiple browser engines.

npm install playwright
npx playwright install chromium
const { chromium } = require('playwright');
(async () => {
  const browser = await chromium.launch();
  try {
    const page = await browser.newPage({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 1 });
    await page.goto('https://example.com', { waitUntil: 'networkidle', timeout: 45000 });
    await page.screenshot({ path: 'page.png', type: 'png', fullPage: true });
  } finally { await browser.close(); }
})();
await page.screenshot({ path: 'viewport.png' });
await page.locator('.invoice').screenshot({ path: 'invoice.png' });
await page.screenshot({ path: 'page.jpg', type: 'jpeg', quality: 85, fullPage: true });

For supplied markup use page.setContent(html). Wait for a readiness selector and document.fonts.ready; prefer this over arbitrary sleeps. If open connections prevent networkidle, use domcontentloaded plus a selector.

3. Puppeteer

Puppeteer exposes page.screenshot() and fits Chromium-focused projects.

npm install puppeteer
const puppeteer = require('puppeteer');
(async () => {
  const browser = await puppeteer.launch({ headless: true });
  try {
    const page = await browser.newPage();
    await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
    await page.goto('https://example.com', { waitUntil: 'networkidle0', timeout: 45000 });
    await page.screenshot({ path: 'page.png', fullPage: true });
    const bytes = await page.screenshot({ type: 'png' });
    require('node:fs').writeFileSync('bytes.png', bytes);
  } finally { await browser.close(); }
})();

4. Choosing between them

Check Question
Browser Do you need Chromium only, or Chromium, Firefox and WebKit?
Scope Viewport, element, or full page?
Readiness Can you wait for selectors, fonts, images, or an app signal?
Runtime Can your container install browsers and support CPU/memory use?
Stack Which library matches existing tests and automation?

The sources establish capabilities, not a universal speed or fidelity winner. Compare both on representative pages if needed.

5. Capture controls

  • Set viewport and device scale before navigation; emulate dark mode or reduced motion when needed.
  • Wait for fonts, images, and lazy content. Disable animations for repeatable output.
  • Use element capture when sticky headers or infinite scroll make full-page output unsuitable.
  • Isolate cookies and contexts; treat URLs and HTML as untrusted.

6. Reliability, performance and cost

  • Reuse one browser process; create a fresh context/page per job.
  • Bound concurrency with a queue and set navigation, selector, and overall timeouts.
  • Retry transient network errors with backoff; always close pages in finally.
  • Record browser version, options, duration, failures, and output size while redacting secrets.

No comparative benchmark was found in the supplied research; measure your own workload.

7. Troubleshooting

Symptom Fix
window is not defined html2canvas ran in Node; move it into a browser or switch libraries.
Executable missing Install the browser during image build and provide a writable cache.
Navigation timeout Use a bounded timeout, domcontentloaded, and a readiness selector.
Blank image Wait for app content, fonts, and lazy images; disable animations.
Element not found Check selector, conditional rendering, and iframe context.
Cross-origin content missing html2canvas follows same-origin restrictions; configure CORS or use browser capture.
Out of memory Lower concurrency, cap dimensions, capture sections, and recycle workers.

8. Or skip the browser setup

ScreenshotNeo returns PNG, JPEG, WebP, or PDF from one GET request. Options include full-page or CSS-element capture, device presets and custom viewports, retina scale, dark mode, custom CSS/JavaScript, waits, cookies, headers, user agent, geolocation, blocking rules, caching, signed links, async jobs, bulk capture, and a usage API. See the docs.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Consent banners, newsletter popups, and chat widgets are removed before capture; each step can be disabled. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, with X-Page-Verdict and X-Billed headers. Its MCP server offers take_screenshot, get_page_info, and capture_pdf. Free includes 1,000 screenshots/month with no card; paid plans start at $5 for 3,000.

Create a free ScreenshotNeo account.

9. FAQ

Why doesn’t html2canvas work in Node.js?

It needs browser globals and layout APIs. Run it in a browser or use automation.

Is Playwright more accurate?

No universal winner is established; both render through a real browser.

Which format?

PNG is lossless, JPEG is smaller for photos, WebP is compact when supported, and PDF is paginated.