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.
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.
