Puppeteer Screenshot Fails with Target Closed: How to Fix It
Fix Puppeteer’s “Target closed” screenshot error by checking page lifecycle, concurrent cleanup, browser crashes, and the failing operation.
Direct answer: Puppeteer’s Target closed error means the DevTools target or its connection closed before the screenshot finished. It is a symptom, not a diagnosis. First make sure page.screenshot() has completed before your code closes the page, browser context, or browser. If the failure only happens on a server or in a container, inspect Chromium’s output and runtime dependencies for a browser crash.
The screenshot API is Page.screenshot(), documented in the Puppeteer screenshot guide. The same error text can arise in different situations, so retain the full stack trace and investigate the operation and runtime that failed rather than assuming one universal fix.
1. Identify what failed
Read the entire rejection and stack trace. Confirm that page.screenshot() itself rejected. A navigation, page creation, cancellation, or cleanup error may occur first and leave a later screenshot call reporting a closed target.
Record the Puppeteer package version, browser version, runtime or container, headless and protocol configuration, the page behavior, and whether other jobs are capturing or closing pages at the same time. A historical issue report illustrates the error and stack trace, but does not establish a universal cause (Puppeteer issue #6610).
2. Await the screenshot before cleanup
Do not let cleanup race the screenshot. Await the capture before closing its page, context, or browser. In particular, check finally blocks, timers, cancellation handlers, signal handlers, and shared cleanup code for an early close.
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
let page;
try {
page = await browser.newPage();
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
// This must finish before page.close() or browser.close().
await page.screenshot({ path: 'shot.png', fullPage: true });
} finally {
if (page && !page.isClosed()) {
await page.close();
}
await browser.close();
}
This example is a lifecycle pattern, not a promise that every target closure has the same cause. If navigation or screenshot work rejects, the finally block still cleans up, but cleanup begins only after the awaited operation has settled.
3. Check concurrent captures and closes
If the problem appears only under load, create a small reproduction and temporarily serialize capture and close operations. Check whether a job manager, timeout, or another task can close a page while a screenshot is pending. A Puppeteer issue reports a protocol timeout with a particular combination of concurrent screenshots and page closure; its results varied with protocol and headless configuration, so it is evidence to investigate ordering rather than a general rule for all versions (Puppeteer issue #12712).
// Diagnostic: run one capture at a time while isolating a race.
for (const url of urls) {
const page = await browser.newPage();
try {
await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.screenshot({ path: `capture-${Date.now()}.png` });
} finally {
if (!page.isClosed()) await page.close();
}
}
If serial execution avoids the failure, add concurrency back gradually and keep page ownership explicit: a task that is still capturing should not share a page with code that can close it. Treat serialization as a diagnostic and a possible workaround for a reproduced race, not as proof that concurrency is inherently unsafe.
4. Investigate server-only failures
If local runs succeed but a server or container fails, look for Chromium process exits and missing runtime dependencies. Enable launch output to see browser-process diagnostics:
const browser = await puppeteer.launch({ dumpio: true });
Check the process logs and the deployment image for unavailable shared libraries or other launch/runtime problems. A discussion about server-side navigation timeouts recommends dumpio: true as a diagnostic; it does not show that missing dependencies cause every Target closed screenshot failure (Puppeteer issue #8071).
Do not add --no-sandbox as a generic fix for this message. The reviewed evidence does not establish that flag as a solution to Target closed. Change one relevant setting at a time and compare the same minimal reproduction.
5. Reproduce and narrow the cause
- Keep the complete error message and stack trace. Identify the first operation that rejects.
- Reduce the script to one browser, one page, one navigation, and one screenshot while preserving the failing runtime and browser configuration.
- Make the screenshot await explicit and temporarily remove concurrent page or browser closure.
- For server-only failures, capture browser output and check the runtime environment.
- Vary one factor at a time, such as headless setting, protocol configuration, or concurrency. Record versions and results.
Increasing a timeout does not restore a target that has already closed. Do not swallow rejected promises: doing so can hide the earlier failure that explains the screenshot error.
6. Screenshot options and practical tradeoffs
For this error, focus first on lifecycle and runtime. Once capture is stable, Puppeteer’s screenshot options can shape the output:
| Option | Use | Consideration |
|---|---|---|
path |
Write image bytes to a file. | Ensure the process can write to that location. |
type |
Select an image format supported by the installed Puppeteer/browser combination. | Check version-specific documentation when using format-specific options. |
fullPage |
Capture the full scrollable page. | Long pages can take longer and use more memory. |
clip |
Capture a specified page rectangle. | Ensure the clip bounds are valid for the rendered page. |
omitBackground |
Request transparency where the format supports it. | JPEG does not support transparency. |
captureBeyondViewport |
Control capture outside the viewport in supported configurations. | Check the Puppeteer version’s API documentation. |
These options affect the capture, but they do not fix a page or browser that closes before capture completes. Use the Page.screenshot API reference for the installed version’s exact option behavior.
7. Reliability, performance, and cost
- Reliability: Give each capture task clear ownership of its page. Await navigation and screenshot promises, and close resources only after the work settles. Log the first rejection and browser-process output when diagnosing server failures.
- Performance: Full-page captures of long pages can require more work than viewport captures. Concurrency can increase resource use and makes lifecycle races harder to isolate. Begin with a serial reproduction, then increase concurrency while measuring your own workload.
- Cost: Puppeteer itself is a browser automation library; operating it in a hosted environment can involve your own compute and maintenance costs. The dossier provides no benchmark or cost figures for Puppeteer, so estimate from your actual runtime and capture volume.
8. Or skip the browser setup
If your goal is a screenshot rather than operating Chromium, ScreenshotNeo provides a website screenshot API and MCP server. A single GET request returns an image or PDF. See the ScreenshotNeo API documentation.
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 removes cookie banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.
Sign up free for 1,000 screenshots a month, no card required.
9. Troubleshooting checklist
| Symptom | Likely line of investigation | Next step |
|---|---|---|
| Error follows a screenshot call | Page, context, browser, or connection may have closed before capture completed. | Await screenshot; inspect cleanup, timers, signals, and cancellation. |
| Error appears only with parallel jobs | Concurrent capture/close ordering may be involved. | Reproduce serially, then add concurrency back gradually. |
| Error appears only on a server | Chromium may exit or the environment may lack runtime dependencies. | Enable dumpio, inspect process output and deployment dependencies. |
| Increasing timeout changes nothing | The target may already be gone; timeout may not be the root problem. | Find the first rejection and verify lifecycle ordering. |
| Failure disappears after changing headless/protocol mode | Configuration may affect reproduction in that specific environment. | Record versions and isolate the setting; do not generalize beyond the reproduction. |
10. FAQ
Does “Target closed” identify which object closed?
No. The message alone does not distinguish a closed page, browser context, browser process, or protocol connection.
Should I retry the screenshot automatically?
Only after determining whether the browser and page are still usable and whether the original operation can be safely repeated. A retry cannot revive a closed target.
Is this always a Puppeteer bug?
No. Issue reports document particular cases and configurations; they do not establish that every occurrence is a Puppeteer defect.
Will --no-sandbox fix it?
The reviewed evidence does not support it as a general fix. Investigate the actual browser exit, environment, or lifecycle race first.


