Put every Puppeteer browser behind a guaranteed cleanup path, and treat Heroku dyno shutdown as a normal event. Close the browser in a finally block, stop accepting work when shutdown starts, finish or cancel active jobs within Heroku’s bounded exit window, and keep Puppeteer’s signal handling enabled unless you have a specific reason to override it. Heroku cycles dynos at least once per day, so no Chromium process should be treated as permanent.
The lifecycle that prevents orphaned Chromium processes
A Chromium process can remain after a request fails when code launches a browser but only closes it on the success path. Exceptions, navigation timeouts, rejected promises and aborted jobs then skip cleanup. The basic invariant is simple: once puppeteer.launch() succeeds, the matching browser.close() must run on success, failure and cancellation.
const puppeteer = require('puppeteer');
async function capture(url) {
let browser;
try {
browser = await puppeteer.launch({
// Add only the runtime arguments your deployment requires.
});
const page = await browser.newPage();
await page.goto(url, {
waitUntil: 'networkidle2',
timeout: 30000
});
return await page.screenshot({ type: 'png', fullPage: true });
} finally {
if (browser) {
await browser.close();
}
}
}
Checking browser matters because launch itself can fail before a browser object exists. The finally block also handles errors thrown by newPage, navigation or screenshot generation. Keep operations bounded: a timeout on navigation, selector waits and other potentially indefinite work gives cleanup a chance to run instead of leaving a job pending forever.
Why closing a page is not enough
page.close() releases one tab, not the Chromium process. A browser can own multiple pages and child processes. Close individual pages when you are finished with them, but close the browser as the final resource action. If a worker reuses one browser for several jobs, track that shared instance explicitly and close it when the worker is stopping.
#1 Best Overall
- Includes Raspberry Pi 5 with 2.4Ghz 64-bit quad-core CPU (8GB RAM)
- Includes 128GB Micro SD Card pre-loaded with 64-bit Raspberry Pi OS, USB MicroSD Card Reader
- CanaKit Turbine Black Case for the Raspberry Pi 5
- CanaKit Low Noise Bearing System Fan
- Mega Heat Sink - Black Anodized
Handle Heroku shutdown as part of normal operation
Heroku sends a dyno shutdown signal when it restarts or stops a process. Heroku’s shutdown guidance illustrates an exit timeout of 30 seconds; work that has not exited by the deadline can be terminated. Your application should therefore enter a draining state immediately rather than accepting new jobs while old ones are still running.
- Mark the process as stopping. Set a flag that causes HTTP handlers and queue consumers to reject or defer new work.
- Stop intake. Pause polling, unsubscribe from new queue deliveries, and stop scheduling new browser launches.
- Let active jobs finish within a deadline. Use a deadline shorter than the platform’s available shutdown period so cleanup still has time to run.
- Cancel work that cannot finish. Abort navigation and other cancellable operations, then close their browsers.
- Exit only after cleanup. Once active work is zero and browser instances are closed, allow the process to terminate.
A minimal worker pattern looks like this:
const puppeteer = require('puppeteer');
let stopping = false;
const active = new Set();
async function runJob(url) {
if (stopping) throw new Error('Worker is draining');
const job = (async () => {
let browser;
try {
browser = await puppeteer.launch({});
const page = await browser.newPage();
await page.goto(url, { waitUntil: 'networkidle2', timeout: 25000 });
return await page.title();
} finally {
if (browser) await browser.close();
}
})();
active.add(job);
try {
return await job;
} finally {
active.delete(job);
}
}
async function shutdown() {
if (stopping) return;
stopping = true;
const deadline = new Promise(resolve => setTimeout(resolve, 25000));
await Promise.race([Promise.allSettled([...active]), deadline]);
process.exit(0);
}
process.once('SIGTERM', shutdown);
process.once('SIGINT', shutdown);
This pattern is intentionally conservative. In a production queue, acknowledge a message only after the browser work succeeds, and make the job retryable when shutdown interrupts it. Do not call process.exit() immediately on a signal: doing so can cut off the finally block and recreate the leak you are trying to prevent. If you need a hard deadline, reserve time for browser closure before the platform deadline.
Use Puppeteer’s signal options deliberately
Puppeteer documents handleSIGHUP, handleSIGINT and handleSIGTERM launch options, with signal handling enabled by default. These options help Puppeteer react when the Node process receives a termination signal, but they do not replace your application’s job lifecycle. Your code still needs to stop intake and guarantee cleanup for every launched browser.
Puppeteer also documents an AbortSignal option for cancellable operations. Pass a signal where the API you use supports it, abort it during shutdown, and retain the finally close as the last line of defense. Avoid disabling Puppeteer’s handlers unless you are intentionally taking over equivalent behavior; otherwise a custom signal setup can leave Chromium alive.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchRank #2
- Includes Raspberry Pi 4 4GB Model B with 1.5GHz 64-bit quad-core CPU (4GB RAM)
- Includes Pre-Loaded 32GB EVO+ Micro SD Card (Class 10), USB MicroSD Card Reader
- CanaKit Premium High-Gloss Raspberry Pi 4 Case with Integrated Fan Mount, CanaKit Low Noise Bearing System Fan
- CanaKit 3.5A USB-C Raspberry Pi 4 Power Supply (US Plug) with Noise Filter, Set of Heat Sinks, Display Cable - 6 foot (Supports up to 4K60p)
- CanaKit USB-C PiSwitch (On/Off Power Switch for Raspberry Pi 4)
Heroku deployment setup: launch requirements versus leak prevention
Heroku does not include every system dependency Puppeteer may need. Puppeteer’s Heroku troubleshooting guidance recommends adding its Heroku buildpack and using --no-sandbox in that deployment context. Those instructions help Chromium start; they do not close a browser after a job. Confirm that the buildpack, Chromium and Node runtime versions match the Puppeteer version installed by your application.
Keep security arguments scoped to your runtime
Use the exact launch arguments required by your Heroku environment and security review. Do not copy a broad argument list from an unrelated host. In particular, treat --no-sandbox as a deployment-sensitive choice, not a universal Puppeteer setting.
Do not import Docker PID 1 advice into every dyno
Puppeteer’s troubleshooting material discusses Docker’s --init or dumb-init for reaping zombie processes when a container’s PID 1 needs that behavior. This is relevant only when your application actually runs in a container setup where PID 1 handling applies. It is not a general fix for a Node process on Heroku’s standard dyno runtime, and it cannot substitute for browser.close().
Concurrency, memory and browser reuse
Launching an unbounded Chromium process for every incoming request can exhaust memory and make shutdown harder. Set an explicit concurrency limit for browser jobs. A queue with a small number of workers is easier to drain than an HTTP handler that launches without back-pressure.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsRank #3
- Not including the Raspberry Pi 5 (8GB), the Crowpi advanced version comes with the Raspberry Pi 5
- ELECROW Black Case for the Raspberry Pi 5, CrowPi is equipped with a 9-inch HD touchscreen along with a camera; All the regular components used in DIY electronics are packed into the CrowPi development board, such as LCD, LED matrix, buzzer, light sensor, PIR sensor, ultrasonic sensor, IR sensor, etc
- Raspberry Pi Sensors: The Crowpi raspberry pi 5 programming kit is jam-packed with lots of buttons such as 19 different sensors in a tidy easy to use package; You don't have to wait and wire things
- Build Quality: Solid ABS shell and well made components in one place make it strong and convenient to travel
- Programming Lessons: This raspberry pi 5 learning kit ships with step by step instructions and provides 21 lessons to take you through identifying components reading code and running it in the terminal
- Decide how many simultaneous browser jobs your dyno can safely run, then enforce that limit.
- Apply navigation, selector and overall job deadlines.
- Reuse a browser only when you can track its ownership and close it during worker shutdown.
- Recycle a browser deliberately after a bounded number of jobs if your application observes growth, rather than relying on an unending process lifetime.
- Never assume a dyno survives indefinitely: Heroku says dynos restart at least daily, at randomized times.
No universal memory limit or safe concurrency number can be stated without your dyno type, page mix, Puppeteer release and application behavior. Measure your own workload and leave headroom for the Node process and Heroku runtime.
Diagnose a process that still appears to hang
1. Confirm whether the browser ever launched
Log a job identifier immediately before and after puppeteer.launch(). If launch fails, the problem is dependency or runtime setup rather than a leaked browser. If it succeeds, log entry and exit of the finally block.
2. Look for a missing cleanup branch
Search every function that launches Chromium. A browser created in a helper but closed only by its caller is easy to orphan when the caller throws. Put ownership and cleanup in the same function whenever possible.
3. Check for shutdown races
Correlate application logs with Heroku logs. If a signal arrives while a new job is being accepted, your drain flag is too late or intake is not checking it. If logs show an exit timeout, inspect pending promises, queue acknowledgements and browser-close completion.
Rank #4
- Fully assembled for plug-and-play operation
- Includes Raspberry Pi 5 with 8GB RAM
- 256 GB PCIe Pi NVMe SSD (Pre-loaded with Pi 64-Bit OS)
- M.2 HAT+
- CanaKit Turbine Black Case for the Pi 5
4. Inspect process restarts
The Heroku CLI can show logs and restart processes, including commands such as heroku logs and heroku ps:restart web. A restart may restore service, but it does not repair a missing cleanup path. Use the restart only as operational recovery while fixing lifecycle code.
5. Verify signal configuration
Check whether code or a dependency disabled Puppeteer’s default signal handling, replaced SIGTERM listeners, or calls process.exit() before asynchronous cleanup. Register shutdown handlers once and make them idempotent.
Performance and reliability choices
| Choice | Benefit | Risk or cost |
|---|---|---|
| Browser per job | Strong isolation between jobs | Higher startup cost and process pressure; requires strict concurrency limits |
| Shared browser | Lower launch overhead | More complex ownership, page isolation and shutdown handling |
| Wait for network idle | More complete pages on many sites | Some pages keep connections open; always retain a timeout |
| Immediate forced exit | Fast termination | Skips asynchronous cleanup and can orphan Chromium |
| Graceful drain | Preserves cleanup and job consistency | Requires intake control and a bounded deadline |
Choose based on workload, then instrument launch duration, job duration, close duration, failures and shutdown state. These timings reveal whether the apparent hang is navigation, application work or browser termination.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Or skip the browser setup
If your goal is simply to obtain clean website screenshots, ScreenshotNeo removes the Heroku Chromium lifecycle from your application. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup step can be disabled. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits cost nothing, and response headers report the page verdict and billing result.
One GET request returns PNG, JPEG, WebP or a PDF. The API supports full-page captures with lazy images, CSS-selector elements, dark mode, device presets, custom viewports, retina scale, PDF paper and page options, custom CSS and JavaScript, clicks, waits, blocked resources, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, selectable cache TTLs, signed image links, asynchronous webhooks, bulk capture of up to 100 URLs per call and usage reporting. An MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.
Best Value
- 【What you Get】You will get 1*Pi 5 8GB Single Board,1*RasTech Case,1*Active Cooler,1*Screwdriver,1*Installation instructions,12-month free warranty, lifetime service, 24-hour prompt and friendly response.
- 【More Connectors】There are two USB 3.0 ports(5Gbps simultaneously) and two USB 2.0 ports, which triple total bandwidth ,support any combination of up to two cameras or displays. Peak SD card performance is doubled through support for the SDR104 high-speed mode. It provides a smooth desktop experience for you. Offer Gigabit Ethernet and a PCIe interface, along with dual-band Wi-Fi and Bluetooth 5.0/BLE wireless capability. The RasTech Pi 5 Kit use the new 27W 5.1V 5A USB-C power connector.
- 【 Support Dual 4Kp60 Display 】Each of the two microHDMI sockets can control a 4K display at 60 Hertz, now support HDR, offering super HD video for media streaming projects. RPi 5 is the first RPi model that comes with a PCI Express port (PCIe 2.0 x1 with 500 MB/s) to attach SSDs (requires separate M.2 HAT).
- 【 Excellent Chips And Applications】Pi 5 is a full-size Pi computer using silicon built in-house at Pi. The RP1 “southbridge” provides the bulk of the I/O capabilities for Pi 5. Pi 5 is more friendly and convenient in the development of Internet of Things, Web development, machine identification, automatic control and other electronic equipment applications and network.
- 【 Faster CPU, Better GPU 】 Pi 5 features a Broadcom BCM2712 64-bit quad-core Arm Cortex-A76 processor running at 2.4GHz, it delivers a 2–3× increase in CPU performance relative to RaspberryPi 4. The 800MHz VideoCore VII GPU is compatible to OpenGL ES 3.1 and Vulkan 1.2, substantial uplift in graphics performance. Pi 5 Offers lightning-fast CPU speed, a PCI Express interface, a Real Time Clock (RTC) and a power button and runs significantly cooler than Pi 4.
cURL
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}`);
See the ScreenshotNeo API documentation for parameters and response headers. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots, and every feature is available on every plan. Create a free ScreenshotNeo account to start.
Frequently Asked Questions
Should I close the page before closing the browser?
Close pages when you no longer need them, then close the owning browser in the same cleanup path. Browser closure is the process-level action that matters.
Can a Heroku restart fix orphaned Chromium permanently?
No. It can recover a dyno operationally, but only guaranteed cleanup, bounded jobs and graceful signal handling prevent recurrence.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Is Docker’s dumb-init required for Puppeteer on Heroku?
Only when the application uses a container setup where PID 1 zombie reaping is relevant. It is not a universal Heroku fix.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




