ScreenshotNeo

BlogHow-to

HTML to Image Tutorials: Generate Website Screenshots and Images

Generate website images with Playwright or html2canvas. Learn when to capture browser pixels, how to target a page or element, and how to handle common rendering issues.

By the ScreenshotNeo team4 October 20268 min read

To turn a website into an image that matches what a visitor sees, render it in a real browser and capture the viewport, full page, or a specific element. Playwright is a good fit for this in Node.js. If you only need a client-side canvas reconstructed from DOM and CSS, html2canvas can work, but its output may differ from the browser’s actual pixels and it has CSS and cross-origin limitations.

This guide shows both approaches, how to choose between them, how to save or process the result, and how to make repeat captures more consistent.

1. Choose the right HTML-to-image approach

Approach Output represents Use it when Main limitations
Playwright screenshot Pixels rendered by a browser You need a website screenshot, viewport, full page, or element capture Browser, operating system, settings, hardware, and headless mode can affect pixels
html2canvas A canvas reconstructed from DOM and applied styles Client-side image generation is sufficient and the page uses supported styles and accessible assets It is not a native screenshot; CSS support is selective and browser content policies still apply

Prefer Playwright when visual fidelity to a rendered website matters. Consider html2canvas when the output can be an approximation and generation needs to happen in the page itself. These methods are not interchangeable: one captures browser output, while the other rebuilds an image from information exposed by the DOM.

2. Capture a website with Playwright

The following runnable Node.js example opens a page, sets a predictable viewport before navigation, waits for the page load event, and saves a PNG screenshot. Install Playwright and its browser first:

npm init -y
npm install playwright
npx playwright install chromium

Save this as screenshot.mjs and run it with node screenshot.mjs https://example.com:

import { chromium } from 'playwright';

const url = process.argv[2] ?? 'https://example.com';
const browser = await chromium.launch({ headless: true });
try {
  const page = await browser.newPage({
    viewport: { width: 1440, height: 900 },
    deviceScaleFactor: 1
  });
  await page.goto(url, { waitUntil: 'load', timeout: 30_000 });
  await page.screenshot({ path: 'page.png', type: 'png' });
} finally {
  await browser.close();
}

Use a viewport matching the layout you want. Set it before navigation because viewport size can change responsive rendering. The path extension determines the output format when supported; Playwright’s screenshot API also documents transparent background output except for JPEG.

Capture only the current viewport

The example above captures the visible viewport by default. This is useful for thumbnails, above-the-fold previews, and reproducing a particular screen size.

Capture the full scrollable page

Set fullPage: true to capture the whole scrollable document as if it were displayed on a very tall screen:

await page.screenshot({ path: 'full-page.png', fullPage: true });

Full-page screenshots can be very tall for long pages. If the result will be sent through a service with image dimension or payload limits, check those limits and consider capturing sections or resizing after capture.

Capture a single element

Use a locator screenshot when you need a chart, card, map container, or another element rather than the whole page:

const card = page.locator('[data-testid="product-card"]');
await card.waitFor({ state: 'visible' });
await card.screenshot({ path: 'product-card.png' });

Choose a selector that identifies one stable element. A missing or ambiguous selector can capture the wrong content or fail; waiting for visibility helps when the element appears after page load.

Return screenshot bytes instead of writing a file

Playwright can return screenshot bytes. This is useful when the next step uploads the image, stores it, or processes it in memory:

const imageBytes = await page.screenshot({ type: 'png' });
// Pass imageBytes to your storage or image-processing code.

3. Render DOM content with html2canvas

html2canvas is a browser-side JavaScript HTML renderer. It reads the DOM and styles and builds a canvas; it does not take an actual screenshot of the browser. Install it in a frontend project:

npm install html2canvas

Then call it from code running in the browser, for example after a button click:

import html2canvas from 'html2canvas';

const element = document.querySelector('#invoice');
if (!element) throw new Error('Could not find #invoice');

const canvas = await html2canvas(element, {
  backgroundColor: '#ffffff',
  scale: window.devicePixelRatio
});
const blob = await new Promise((resolve, reject) => {
  canvas.toBlob((value) => value ? resolve(value) : reject(new Error('Canvas export failed')), 'image/png');
});
const link = document.createElement('a');
link.download = 'invoice.png';
link.href = URL.createObjectURL(blob);
link.click();
URL.revokeObjectURL(link.href);

This code must run in a browser context with the target element present. The library’s documentation says CSS support depends on properties it has implemented, and that its output may not be 100% accurate to the real representation. Check the specific page’s styles and browser support before relying on the result.

Cross-origin images and fonts

Browser content policies still apply. A DOM-to-canvas renderer does not bypass cross-origin restrictions. If an image or other resource is served from another origin, it may not be available for a clean canvas export. Configure the resource server to permit access where appropriate, or use a proxy that returns the resource in a form the page can use, such as a base64 data URI. Do not assume a URL that displays in the page can always be exported into a canvas.

4. Make screenshots repeatable

For visual comparisons, use the same browser version, operating system, viewport, device scale factor, settings, and headless mode between captures. Playwright Test can create a screenshot baseline on its first run and compare later screenshots against it, but the official documentation cautions that rendering can vary with the host environment and hardware.

  1. Fix the viewport and device scale factor before navigation.
  2. Use the same browser and execution environment for baseline and later runs.
  3. Wait for the content that matters, such as a locator becoming visible, instead of relying only on a short arbitrary delay.
  4. Reduce changing content when deterministic comparison matters, such as rotating banners or timestamps, using application fixtures or page-specific styling where appropriate.
  5. Review diffs in context: a changed font, browser version, or environment can affect pixels even when application code has not changed.

5. Or skip the browser setup

For a hosted screenshot API, ScreenshotNeo accepts a URL in one GET request and returns an image or PDF. Create an API key, then use the request below. See the ScreenshotNeo API documentation for parameters and response details.

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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const bytes = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));
  • Cookie banners are accepted and 60+ known consent platforms, newsletter popups, and chat widgets are removed before the shot; each step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing. Response headers say the page verdict and whether the request was billed.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.
  • The Free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots; every feature is on every plan.

Learn about ScreenshotNeo or sign up for 1,000 free screenshots a month with no card.

6. Troubleshooting

Symptom Likely cause What to do
Playwright screenshot is blank or incomplete The page has not rendered the content you need when capture runs Wait for the relevant locator or application-ready condition before taking the screenshot.
The layout has the wrong breakpoints Viewport dimensions were set after navigation or do not match the target Set the viewport before navigation and use the dimensions for the intended layout.
Element capture fails The selector matches no visible element, or content is still loading Check the selector, wait for the locator to become visible, and confirm the element exists on that route.
html2canvas output differs from the browser The library reconstructs from DOM and supported styles rather than capturing browser pixels Check its documented CSS support; use Playwright if accurate browser output is required.
Images are missing from html2canvas output A cross-origin policy prevents the resource from being included in the exported canvas Serve the asset with suitable cross-origin access or use an appropriate proxy; verify the page’s content policy.
Screenshot baselines change on another machine Browser, OS, hardware, settings, or headless mode differs Keep the capture environment consistent and account for dynamic page content.
The output file is unexpectedly large or slow to handle A full-page or high-density capture creates many pixels Capture only the needed region, use an appropriate device scale factor, or resize the result in a later image-processing step.

7. Performance, reliability, and cost

Local Playwright capture requires a browser installation and the compute and storage for each job. Full-page captures and high device scale factors produce more pixels and can take more memory to handle. Capture the smallest area and resolution that meets the use case, and reuse a browser process for batches where your application architecture allows it.

For reliability, use explicit navigation and element readiness conditions, bounded timeouts, and cleanup in a finally block so browser processes close on errors. Treat pages with authentication, unstable content, blocked resources, or bot checks as distinct cases that may need page-specific handling. Screenshot output is environment-dependent, so keep the runtime stable for visual comparisons.

With ScreenshotNeo, the Free plan has 1,000 shots per month, Starter is $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000. Yearly billing gives two months free. Only clean shots are billed; bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are free. The response includes verdict and billing headers so a caller can distinguish these outcomes.

8. Frequently asked questions

Can I turn an HTML string into an image?

Yes. Put the markup in a browser page and capture it with Playwright, or render the relevant DOM with html2canvas in a browser. Choose based on whether you need browser pixels or a DOM-based canvas rendering.

Can html2canvas run in Node.js?

The project describes html2canvas as browser-dependent and unsuitable for Node.js. Use browser automation such as Playwright for server-side website screenshots.

Should I use PNG, JPEG, or WebP?

Choose based on the downstream use: PNG is a common choice for crisp interface captures, while JPEG is useful when a lossy photo-oriented output is acceptable. Confirm the format options of the capture method you use; html2canvas produces a canvas that can be exported in supported browser formats.

Does a full-page screenshot include content loaded as I scroll?

Playwright documents full-page capture as the entire scrollable page shown on a very tall screen. Pages that load content only after scrolling may need an application-specific preparation step before capture.

Why do visual snapshots differ when nothing changed?

Browser rendering depends on the operating system, browser version, settings, hardware, and headless mode. Keep those stable and account for dynamic content when comparing images.