ScreenshotNeo

BlogHow-to

Can Puppeteer Take Website Screenshots Without a Visible Browser?

Yes. Puppeteer runs headless by default, so you can capture website screenshots without displaying a browser window.

By the ScreenshotNeo team4 October 20266 min read

Yes. Puppeteer runs in headless mode by default, so it can take website screenshots without showing a browser window. Launch Puppeteer, open a page, navigate to a URL, and call page.screenshot(). A browser still runs behind the scenes; only its visible interface is absent.

Take a screenshot in headless mode

Install Puppeteer in a Node.js project. The puppeteer package downloads a compatible Chrome for Testing browser by default. The example below sets the viewport explicitly, waits for navigation, captures the full page, and closes the browser even if navigation or capture fails.

npm install puppeteer
import puppeteer from 'puppeteer';

const browser = await puppeteer.launch(); // Headless by default.
try {
  const page = await browser.newPage();
  await page.setViewport({ width: 1280, height: 800 });
  await page.goto('https://example.com', { waitUntil: 'networkidle2' });
  await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
  await browser.close();
}

Save the code as an ES module, such as screenshot.mjs, and run node screenshot.mjs. Puppeteer’s default launch behavior is headless; this is equivalent to puppeteer.launch({ headless: true }). The screenshot guide documents navigation and capture options in the Puppeteer screenshot guide and Page.screenshot API.

Choose headless, headful, or headless shell

Launch setting What it does When to use it
headless: true (default) Runs current headless Chrome without a visible browser window. Routine screenshots, CI jobs, servers, and automated capture.
headless: false Runs Chrome with a visible window. Debugging interactions or watching a page load locally.
headless: 'shell' Uses the separate chrome-headless-shell executable. Automation that may benefit from its performance and does not need all Chrome features.

Current headless Chrome and headful Chrome use the same browser code path, while the older headless implementation is a separate shell and may not match regular Chrome completely. Choose the shell only when its feature set fits the task. Puppeteer documents the distinction in its headless modes guide and supported browsers page.

// Show a visible Chrome window for local debugging:
const browser = await puppeteer.launch({ headless: false });

// Or explicitly request current headless Chrome:
const browser = await puppeteer.launch({ headless: true });

Enabling DevTools forces headful mode. If the reason for headless capture is simply to avoid a visible window, do not enable DevTools.

Set dimensions and capture the right area

Set a viewport that matches the layout you want to capture. Headless Chrome’s default screen size is 800 × 600 unless a window size is specified; setting the page viewport explicitly makes screenshot dimensions and responsive layout more predictable.

await page.setViewport({ width: 1440, height: 900 });

// Viewport screenshot:
await page.screenshot({ path: 'viewport.png' });

// Entire document, including content below the fold:
await page.screenshot({ path: 'full-page.png', fullPage: true });

// A rectangular region in CSS pixels:
await page.screenshot({
  path: 'region.png',
  clip: { x: 0, y: 0, width: 640, height: 400 }
});

// Transparent background where the page permits it:
await page.screenshot({ path: 'transparent.png', omitBackground: true });

fullPage captures beyond the viewport, clip selects a region, and omitBackground requests transparency. For pages with lazy-loaded images, scrolling through the page before a full-page capture can help trigger loading; wait for the images or application state you need rather than assuming navigation alone means all content is ready.

Wait for the page you actually need

The waitUntil option on page.goto() controls when navigation is considered complete. networkidle2 waits for network activity to quiet, but analytics, streaming, polling, and other persistent requests can prevent an idle condition. For those pages, use a less restrictive navigation condition and wait for a meaningful selector or a bounded delay.

await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('main article');
await page.screenshot({ path: 'article.png', fullPage: true });

Use a selector that indicates the content is ready, not merely that the document exists. Set a timeout appropriate to the target and handle failures so a slow or unavailable site does not leave a browser process running.

Use Puppeteer with a managed browser

puppeteer downloads Chrome for Testing by default. Use puppeteer-core when your team manages the browser executable or connects to a remote browser; in that case, configure the executable or connection according to your environment. Headless mode removes the UI, but it does not remove the requirement for a compatible browser binary, operating system libraries, and runtime setup. See Puppeteer’s installation guide.

Deployment, performance, and reliability

  • Close browser instances. Use try/finally around capture work. In a long-running service, reuse a browser process where appropriate and create/close pages per job to avoid repeatedly paying browser startup cost.
  • Bound waits. Navigation and selector waits can fail on slow or unreachable sites. Choose timeouts and retry policy based on your workload, and avoid retrying indefinitely.
  • Account for Linux dependencies. Containers and minimal Linux images may lack libraries Chrome needs. Follow Puppeteer’s deployment guidance and install the required dependencies for the chosen browser.
  • Keep the sandbox enabled when possible. Puppeteer’s troubleshooting guide strongly discourages disabling Chrome’s sandbox except when opening trusted content. Do not treat --no-sandbox as a routine fix.
  • Consider fidelity and features. Current headless Chrome shares the regular Chrome code path. The shell may be faster for some automation, but it is a different executable and does not completely match regular Chrome.
  • GPU behavior differs by mode. The chrome-headless-shell requires --enable-gpu for GPU acceleration. See the official Puppeteer troubleshooting guide.
  • Cost depends on infrastructure. Puppeteer itself is a library; your operational costs come from the machines or browser infrastructure, storage, and network usage you provide. The research sources establish no universal benchmark or price for a given capture.

Or skip the browser setup

If you need screenshots without installing and operating Chrome, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. Its capture flow accepts cookie and consent banners like a visitor, then removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. An MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs.

See the ScreenshotNeo API documentation. Example cURL request:

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,
)
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 request failed: ${res.status}`);
await import('node:fs/promises').then(({ writeFile }) =>
  writeFile('shot.webp', Buffer.from(await res.arrayBuffer()))
);

ScreenshotNeo includes 1,000 screenshots per month free with no card; paid plans start at $5 for 3,000. Sign up for the free plan.

Troubleshooting

Symptom Likely cause Fix
No screenshot file appears The script failed before capture, the path is unexpected, or an error was not handled. Log navigation and screenshot errors, use an explicit output path, and confirm the process reaches page.screenshot().
Chrome fails to launch in a container Missing system libraries, browser executable, or incompatible runtime setup. Use Puppeteer’s installation and troubleshooting guidance for your OS and browser build.
Navigation hangs at networkidle2 The page keeps requests open or continuously sends traffic. Wait for domcontentloaded or another suitable condition, then wait for the specific content selector.
Screenshot is blank or incomplete The capture happened before client-rendered content or images were ready. Wait for an application-specific selector, and trigger lazy loading if the required content is below the fold.
Layout differs from the expected size The browser viewport was left at its default or responsive breakpoints changed the layout. Set the viewport explicitly before navigation and capture.
Sandbox-related launch error The environment’s user or container configuration conflicts with Chrome sandboxing. Correct the container/user setup using Puppeteer’s deployment guidance. Disabling the sandbox reduces protection and is not a routine fix.
Headless shell output differs from Chrome The shell is a separate legacy headless executable with different behavior. Use current headless Chrome with headless: true when regular Chrome behavior or features matter.

FAQ

Does Puppeteer need a monitor in headless mode?

No visible monitor or browser window is needed, but a compatible browser process still runs on the machine or remote runtime.

Can Puppeteer return screenshot bytes instead of saving a file?

Yes. Call page.screenshot() without a path; it returns image bytes that your program can store or send elsewhere.

How do I make Puppeteer show the browser?

Launch with await puppeteer.launch({ headless: false }). This is useful for local debugging and requires a display environment.

Is headless mode a different browser?

Current headless Chrome uses the regular Chrome code path. Puppeteer’s 'shell' mode uses a separate executable.

Does full-page capture automatically load every lazy image?

Do not assume so. Trigger the page’s lazy-loading behavior and wait for the required images before capturing.