Taking Screenshots with Puppeteer: A Practical Guide
Learn how to capture web page screenshots with Puppeteer and headless Chrome in Node.js — from basic usage to full-page captures, viewports, and production pitfalls.
Puppeteer is Google's Node.js library for controlling headless Chrome. It's one of the most popular tools for taking screenshots of web pages programmatically, and for good reason — it gives you full control over a real browser.
In this guide, we'll walk through how to capture screenshots with Puppeteer, from the basics to more advanced techniques, and cover some of the challenges you'll hit when running it in production.
Getting Started
Install Puppeteer in your Node.js project. It downloads a bundled version of Chromium automatically:
npm install puppeteer
If you want to use an existing Chrome installation instead, install puppeteer-core and provide the executable path yourself.
Here's the simplest possible screenshot:
const puppeteer = require('puppeteer'); (async () => { const browser = await puppeteer.launch(); const page = await browser.newPage(); await page.goto('https://example.com'); await page.screenshot({ path: 'screenshot.png' }); await browser.close(); })();
That's it — five lines to capture a web page as a PNG. Puppeteer launches a headless Chrome instance, navigates to the URL, saves the screenshot, and shuts down.
Setting the Viewport Size
By default, Puppeteer uses an 800x600 viewport. For most real-world uses, you'll want to change this. Set the viewport before navigating:
await page.setViewport({ width: 1280, height: 720 }); await page.goto('https://example.com'); await page.screenshot({ path: 'screenshot.png' });
For high-DPI screenshots (like Retina displays), set the deviceScaleFactor:
await page.setViewport({ width: 1280, height: 720, deviceScaleFactor: 2 });
This produces a 2560x1440 image — useful when you need crisp screenshots for documentation or marketing pages.
Full-Page Screenshots
By default, Puppeteer only captures the visible viewport. To capture the entire scrollable page, set fullPage to true:
await page.screenshot({ path: 'full-page.png', fullPage: true });
Keep in mind that full-page screenshots of long pages can produce very large images. A page with a lot of content might generate a PNG that's several megabytes.
For a deeper look at handling lazy-loaded content, infinite scroll, sticky headers, and memory optimization, see our complete guide to Puppeteer full-page screenshots.
Capturing a Specific Element
Sometimes you only need a screenshot of a particular section — a chart, a card, or a hero banner. Puppeteer lets you screenshot a single element:
const element = await page.$('.pricing-table'); await element.screenshot({ path: 'pricing.png' });
This clips the screenshot to the bounding box of that element, which is handy for generating images of specific components.
Waiting for Content to Load
One of the trickiest parts of taking screenshots is making sure the page is actually ready. Dynamic content, lazy-loaded images, and client-side rendering can all cause incomplete captures.
Puppeteer's goto method accepts a waitUntil option:
await page.goto('https://example.com', { waitUntil: 'networkidle0' // wait until no network requests for 500ms });
The available options are:
load— fires when theloadevent triggers (default)domcontentloaded— fires when the HTML is parsednetworkidle0— no more than 0 network connections for 500msnetworkidle2— no more than 2 network connections for 500ms
For most screenshot use cases, networkidle0 gives the best results. But some pages with persistent connections (analytics, WebSockets) will never reach networkidle0. In those cases, use networkidle2 or add an explicit wait:
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' }); await page.waitForSelector('.hero-image', { visible: true }); await page.screenshot({ path: 'screenshot.png' });
Output Formats
Puppeteer supports PNG, JPEG, and WebP:
// JPEG with quality setting (smaller file size) await page.screenshot({ path: 'screenshot.jpg', type: 'jpeg', quality: 80 }); // WebP for modern browsers await page.screenshot({ path: 'screenshot.webp', type: 'webp', quality: 85 });
JPEG and WebP produce significantly smaller files than PNG, which matters if you're generating images at scale.
Generating PDF Output
Puppeteer can also generate PDFs, which is useful for invoices, reports, or printable documents:
await page.pdf({ path: 'page.pdf', format: 'A4', printBackground: true });
Common Production Challenges
Getting Puppeteer working locally is straightforward. Running it in production is where things get harder.
Memory and Resource Usage
Each headless Chrome instance uses 50-300 MB of RAM depending on the page. If you're handling multiple concurrent requests, memory adds up fast. You'll need to carefully manage browser instances — reusing them across requests while handling crashes and memory leaks.
// Reuse a single browser instance let browser; async function getScreenshot(url) { if (!browser) { browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] }); } const page = await browser.newPage(); try { await page.goto(url, { waitUntil: 'networkidle0', timeout: 30000 }); return await page.screenshot({ type: 'png' }); } finally { await page.close(); } }
Zombie Processes
Chrome processes can become orphaned if your application crashes or doesn't clean up properly. Over time, these zombie processes consume all available memory on the server. You'll want process monitoring and cleanup scripts to handle this.
Font Rendering
Headless Chrome on Linux renders fonts differently than on macOS or Windows. If your screenshots look off, you likely need to install font packages on the server:
apt-get install fonts-liberation fonts-noto-color-emoji fonts-noto-cjk
Even with the right fonts installed, rendering can differ between environments — a frustrating issue when screenshots look perfect locally but wrong in production.
Server Dependencies
Running Chrome on a server requires system-level dependencies (libx11, libxcomposite, libasound2, and others). Docker helps here, but adds another layer of infrastructure to manage. The official Puppeteer Docker images are a good starting point, but you'll still need to handle scaling, health checks, and resource limits.
Scaling
A single server can handle perhaps 5-10 concurrent screenshot requests before performance degrades. Beyond that, you need a queue system, load balancing, and multiple rendering nodes. This is where a simple "take a screenshot" requirement can turn into a distributed systems problem.
If you're evaluating alternatives, we've written a side-by-side comparison of Puppeteer and Playwright for screenshots, and a comparison of screenshot API services for when you want to skip browser management entirely.
When to Consider an API Instead
Puppeteer is a great tool if you need deep browser automation — filling out forms, clicking through flows, scraping content. For those use cases, there's no substitute for having full control.
But if your primary need is capturing web page screenshots — for social cards, link previews, documentation images, or visual testing — managing headless Chrome infrastructure is a significant ongoing cost. You're spending time on browser management instead of your actual product.
RenderScreenshot handles the browser infrastructure so you don't have to. A single API call replaces the Puppeteer boilerplate, process management, font configuration, and scaling concerns:
curl "https://api.renderscreenshot.com/v1/screenshot?url=https://example.com&width=1280&height=720" \ -H "Authorization: Bearer rs_live_..."
No Chrome instances to manage, no zombie processes, no font issues across environments. Screenshots are captured on edge infrastructure and cached on a global CDN.
You can sign up for free and get 50 credits to try it out — enough to see if it fits your workflow before committing.
Related Guides
- Puppeteer Full Page Screenshot: The Complete Guide — Handling lazy content, sticky elements, and memory limits
- How to Screenshot Single Page Applications — Wait strategies for React, Vue, and Angular apps
- Puppeteer Timeouts and Memory Leaks: A Debugging Guide — Systematic solutions for production screenshot issues
- Scaling Puppeteer Screenshots — Concurrency, Docker, and performance optimization
- Puppeteer vs Playwright: A Screenshot Comparison — How Playwright compares for screenshot tasks
- Best Screenshot API Comparison — When to use an API instead of running your own browser
Have questions about migrating from Puppeteer? Check our documentation or reach out at [email protected].