ScreenshotNeo

BlogComparisons

wkhtmltoimage vs Headless Chrome: Which Captures Modern Websites Better?

For interactive sites and Chrome-aligned rendering, Headless Chrome is the stronger default. Keep wkhtmltoimage when its simpler rendering fits your pages and workflow.

By the ScreenshotNeo team4 October 20269 min read

Short answer: For modern, interactive websites—and when you need output aligned with current Chrome—choose Headless Chrome. Keep wkhtmltoimage when it is already part of a stable workflow or your pages are simple and its output meets your needs. This is a capability-based recommendation, not the result of a controlled speed or image-quality benchmark.

The key difference is the rendering engine. The wkhtmltoimage project describes its tool as using Qt WebKit. Current Chrome Headless is unified with headful Chrome. Puppeteer can automate Chrome and expose page interaction and capture controls; it is an automation library, not a rendering engine. wkhtmltoimage project overview, Chrome Headless documentation, Puppeteer documentation.

1. What each tool renders

wkhtmltoimage

wkhtmltoimage is an open-source command-line tool from the wkhtmltopdf project. It renders HTML to image formats using Qt WebKit and can run without a display service. It may be a practical choice when you have an existing workflow and the pages you capture render acceptably in that environment.

Headless Chrome

Headless Chrome runs Chrome without a visible browser window. Chrome’s documentation says the current Headless mode is unified with headful Chrome. The older, separate Headless implementation is available as the standalone chrome-headless-shell beginning with Chrome 132.0.6793.0. When comparing results, make sure you know which mode and binary your deployment uses.

Puppeteer is the automation layer

Puppeteer is a JavaScript library for automating Chrome and Firefox. It can navigate, query and click elements, type, intercept network requests, take screenshots, and create PDFs. It is useful when a capture depends on actions or explicit readiness conditions. The browser still performs the rendering.

Do not confuse Qt WebKit with Qt WebEngine: Qt’s current WebEngine is built on Chromium, but it is not the engine identified by the wkhtmltoimage project. Qt WebEngine overview.

2. Which captures modern websites better?

For a site built around JavaScript, changing page state, or browser interaction, Headless Chrome with Puppeteer is the more suitable default. It gives you current Chrome rendering and documented controls for interacting with the page before capture. For a static page or an established workflow that already produces acceptable output, wkhtmltoimage may remain sufficient.

Need Better starting point Reason
Match current Chrome rendering Headless Chrome Current Headless mode is unified with headful Chrome.
Click, type, or inspect elements before capture Puppeteer with Chrome Puppeteer documents page interaction and screenshot automation.
Existing command-line pipeline for simple pages wkhtmltoimage It is a command-line renderer and may already fit the workflow.
Know exactly when delayed content is ready Puppeteer with Chrome You can wait for a selector or other page condition before taking the screenshot.

These are capability-based choices. The available documentation does not establish a universal speed ranking, a pixel-accuracy winner for every page, or a claim that one tool succeeds on every modern CSS feature. Your fonts, installed browser build, operating environment, viewport, page state, and readiness rule all affect the result.

3. Capture a representative page with wkhtmltoimage

Install the wkhtmltoimage binary through the package or release channel appropriate for your operating system, then capture a page with the command-line interface. The exact switches available depend on the version; consult the project documentation and the manual for the binary you installed.

wkhtmltoimage https://example.com screenshot.png

Replace the URL and output filename with your own. Before adopting the result, check the actual output for layout, fonts, images, delayed content, and the page state you intended to capture. If a page relies on interaction or late-running scripts, determine whether your version and invocation can reliably reach the desired state; do not assume the screenshot command has captured a settled application just because it returned an image.

4. Capture a page with Headless Chrome

Chrome’s command-line interface includes timeout and virtual-time controls. Those can help bound a capture or advance time-dependent page behavior, but they do not replace checking that the content you need has appeared. See the Headless documentation and command-line reference for supported options in your Chrome version.

chrome --headless --screenshot=screenshot.png --window-size=1440,1000 https://example.com

The executable name can vary by operating system and installation. If it is not on your path, use the installed Chrome binary’s full path. This basic command is appropriate for a straightforward page. For a dynamic application where you need to wait, click, or inspect the document, use Puppeteer.

5. Runnable Puppeteer example for a dynamic page

This example launches Chrome, opens a URL, waits for a page-specific element to become visible, then saves a screenshot. Use a selector that indicates the content you actually need. The example requires Node.js and Puppeteer installed in your project.

npm install puppeteer
// capture.mjs
import puppeteer from 'puppeteer';

const url = process.argv[2] ?? 'https://example.com';
const browser = await puppeteer.launch({ headless: true });

try {
  const page = await browser.newPage();
  await page.setViewport({ width: 1440, height: 1000, deviceScaleFactor: 1 });
  await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
  await page.waitForSelector('main', { visible: true, timeout: 30000 });
  await page.screenshot({ path: 'screenshot.png', fullPage: true });
} finally {
  await browser.close();
}

Run it with node capture.mjs https://example.com. Replace main with a selector that appears only when the relevant content is ready. The finally block closes Chrome even if navigation or capture fails.

Interact before capture

If the content is behind a control, perform the interaction before waiting for the resulting content. For example:

await page.locator('button[data-action="load-results"]').click();
await page.waitForSelector('.results-table', { visible: true, timeout: 30000 });
await page.screenshot({ path: 'results.png', fullPage: true });

Use selectors tied to stable application markup where possible. A selector that matches an unrelated hidden element or changes between releases can make the capture flaky.

Choose a readiness condition deliberately

  • domcontentloaded waits for document parsing, but images, fonts, and later application work may still be pending.
  • load waits for the load event, but a single-page application can continue fetching or changing content after it fires.
  • networkidle can be useful for quiet pages, but polling, analytics, or streaming connections may prevent network idleness. It can also occur before application-specific work finishes.
  • waitForSelector or an application-specific condition is often the clearest signal that the desired content is present.

For pages that intentionally render after a delay, wait for the expected element or use a bounded delay only when the page offers no better signal. A fixed sleep adds latency on fast responses and can still be too short on slow ones.

6. Compare the tools fairly on your own pages

Use a small, reproducible test set before migrating a production capture pipeline. This is a suggested evaluation plan, not a report of tests performed for this article.

  1. Choose representative URLs: a simple document, a JavaScript-heavy page, a page with delayed images or content, and a page that needs interaction.
  2. Use the same viewport dimensions and, where relevant, the same device scale factor.
  3. Run captures in the same operating environment with the intended installed fonts and browser build.
  4. Set an explicit readiness condition for each page rather than comparing one settled capture with one early capture.
  5. Inspect layout, line wrapping, fonts, images, lazy-loaded content, and whether the final application state is correct.
  6. Repeat captures to see whether the result is stable. Record browser and tool versions, options, duration, and any failures.
  7. Estimate operational cost from your own run time, infrastructure, and maintenance effort; the cited documentation does not establish a speed or cost benchmark between these tools.

7. Migration considerations

Replacing wkhtmltoimage with Chrome is not just a binary swap. You may need to package a browser, ensure compatible libraries and fonts are installed, choose a Chrome version, and set explicit navigation and readiness behavior. If you already depend on wkhtmltoimage options, map each requirement to a supported Chrome CLI or Puppeteer feature and verify representative outputs.

For a low-risk transition, run both paths against a sample set, compare the actual images, and switch only after the new path meets your requirements for state, layout, and repeatability. Keep the old path available during rollout if your operational process benefits from a rollback route.

8. Troubleshooting

Symptom Likely cause What to try
Screenshot shows a loading state or missing app content Capture occurred before application rendering finished. Wait for a page-specific selector or condition. Do not rely on navigation completion alone for a single-page app.
Some images or lazy content are missing The page had not loaded or scrolled the relevant content into view. Wait for the required assets or trigger the page behavior that loads them before capture; verify the result.
Capture hangs or times out Navigation, a resource, or an overly broad network-idle condition never completed. Set a finite timeout, use a narrower readiness condition, and investigate the page’s network behavior.
Chrome command is not found Chrome is not installed at the expected executable path. Install the intended browser build or invoke its full path. Check which binary your deployment will use.
Screenshot differs between machines Different browser versions, fonts, viewport, device scale, or page state. Pin the environment inputs you control and record them with the capture.
Puppeteer cannot find a selector The selector is wrong, appeared later than expected, or belongs to a different frame. Inspect the page markup, wait for the correct selector, and account for iframe content where applicable.
wkhtmltoimage output differs from Chrome The tools use different rendering lineages and may not produce identical results. Compare the page against the renderer required by your use case. Check fonts, layout, and final state on your representative URLs.
Command-line screenshot is blank The target page may not have rendered useful content by capture time, or navigation may have failed. Check that the URL is reachable from the capture environment and that the page is ready before relying on the image.

9. Performance, reliability, and cost

The reviewed sources do not provide a controlled head-to-head benchmark, so there is no supported claim here that either tool is always faster or cheaper. Measure the workload you plan to run: capture duration, browser startup, memory use in your environment, failure rate, and the maintenance cost of keeping the renderer and dependencies working.

For reliability, make captures deterministic where possible: fix viewport and device scale, use explicit readiness checks, bound timeouts, record tool versions, and handle navigation or selector failures as errors rather than silently saving a misleading image. Retry only failures likely to be transient, and keep retries bounded.

For a high-volume service, account for browser process management and deployment needs alongside per-capture time. A command-line renderer may fit a simple existing job, while browser automation offers controls for pages that need interaction. Which is less expensive depends on your page mix and infrastructure.

10. Or skip the browser setup

If you need screenshots without installing and maintaining a browser workflow, ScreenshotNeo is a website screenshot API and MCP server. Make one GET request with a URL to receive a PNG, JPEG, WebP, or PDF. The API documentation describes the request options.

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}`);
await Bun.write('shot.webp', res);

ScreenshotNeo accepts cookie and consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 shots a month with no card; paid plans start at $5 for 3,000 shots. Every feature is on every plan.

Sign up free for 1,000 screenshots a month, with no card required.

11. FAQ

Is Puppeteer another browser engine?

No. It automates browsers such as Chrome and Firefox; the browser does the rendering.

Does Chrome Headless mean the old Headless Shell?

Not necessarily. Current Headless mode is unified with Chrome, while the older implementation is available as a separate shell binary beginning with Chrome 132.0.6793.0.

Should I replace a working wkhtmltoimage pipeline?

Only if your requirements call for Chrome-aligned rendering, interaction, or automation controls that your current workflow does not meet. Compare your own representative pages before changing a production path.

Is Chrome guaranteed to look better on every URL?

No universal result is established here. The recommendation follows the rendering lineage and automation capabilities, not a benchmark across all sites.