DEV Community

Custodia-Admin
Custodia-Admin

Posted on • Originally published at pagebolt.dev

How to Take Screenshots in Node.js: Puppeteer vs API Comparison

You need to take screenshots in your Node.js app. Maybe you're:

  • Building a link preview service
  • Auto-generating OG images for social sharing
  • Testing your website across devices
  • Archiving web pages for compliance
  • Monitoring competitor pricing pages

You search for "Node.js screenshot" and find Puppeteer. It's open source. It's free. It's got 85k GitHub stars.

Six hours later, you're still debugging Chrome binary paths, memory leaks, and server crashes.

There's a better way. Let me show you both approaches — Puppeteer and a hosted API — so you can decide which fits your needs.

The Puppeteer Approach

Puppeteer is a Node.js library that controls Chrome programmatically. Here's the minimal working example:

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: 'example.png' });
  await browser.close();
})();
Enter fullscreen mode Exit fullscreen mode

That's 9 lines. Looks simple, right?

But this is the honeymoon phase. In production, you'll need:

1. Browser Management

const browser = await puppeteer.launch({
  headless: true,
  args: [
    '--no-sandbox',  // Required on Linux servers
    '--disable-setuid-sandbox',
    '--disable-dev-shm-usage',  // Prevent memory issues
    '--single-process',  // Risk: one crash kills all tabs
  ]
});
Enter fullscreen mode Exit fullscreen mode

2. Error Handling & Timeouts

const browser = await puppeteer.launch({ timeout: 30000 });
const page = await browser.newPage();
page.setDefaultTimeout(10000);
page.setDefaultNavigationTimeout(10000);

try {
  await page.goto(url, { waitUntil: 'networkidle2' });
} catch (error) {
  console.error('Navigation failed:', error);
  // What now? Retry? Log? Alert?
}
Enter fullscreen mode Exit fullscreen mode

3. Memory & Resource Management

// Prevent memory leaks
await page.close();
await browser.close();

// But what if the request times out?
// What if the user disconnects?
// You need try/finally blocks everywhere
Enter fullscreen mode Exit fullscreen mode

4. Concurrency

// You can't reuse the same browser for multiple requests safely
// So you create a pool:

const pool = [];
for (let i = 0; i < 10; i++) {
  pool.push(puppeteer.launch());
}

// Now manage which browser handles which request
// Handle browser crashes gracefully
// Respawn dead browsers
// This is essentially a process manager
Enter fullscreen mode Exit fullscreen mode

5. The Real Cost

Once you have Puppeteer working on your local machine, here's what happens in production:

  • Server costs: A single screenshot takes 200–500MB of RAM. With 10 concurrent users, you need 2–5GB of server memory. Add a load balancer, horizontal scaling, and you're looking at $1,000+/month just for the infrastructure.
  • Maintenance: Chrome updates break your setup. Your Node version matters. Your server OS matters. You become a DevOps engineer.
  • Monitoring: You need to watch for zombie Chrome processes, memory leaks, crashed browsers. One bad website (infinite JavaScript, memory bomb) crashes the entire service.
  • Reliability: If your server goes down, your screenshot service is down. No redundancy. No failover.

Real-world Puppeteer cost at 10,000 screenshots/month:

  • Infrastructure: $500/month (dedicated server, 4GB RAM)
  • Operational labor: $3,000/month (on-call, monitoring, incident response)
  • Total: $3,500/month

That's before you add features like styled screenshots, device presets, or PDF generation.

The API Approach

Here's the same task with a hosted screenshot API:

const response = await fetch('https://api.pagebolt.io/api/v1/screenshot', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer YOUR_API_KEY',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    url: 'https://example.com'
  })
});

const buffer = await response.arrayBuffer();
const fs = require('fs');
fs.writeFileSync('screenshot.png', Buffer.from(buffer));
Enter fullscreen mode Exit fullscreen mode

That's it. 13 lines, including imports and error handling. No browser management. No memory leaks. No server crashes.

But let's be honest — a 5-line example is boring. Here's what a real implementation looks like:

const express = require('express');
const fetch = require('node-fetch');
const fs = require('fs');

app.post('/api/screenshot', async (req, res) => {
  const { url } = req.body;

  try {
    const response = await fetch('https://api.pagebolt.io/api/v1/screenshot', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.PAGEBOLT_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        url,
        width: 1280,
        height: 720,
        deviceScaleFactor: 2,
        blockAds: true,
        fullPage: false
      })
    });

    if (!response.ok) {
      throw new Error(`API error: ${response.status}`);
    }

    const buffer = await response.arrayBuffer();
    res.setHeader('Content-Type', 'image/png');
    res.send(Buffer.from(buffer));
  } catch (error) {
    console.error('Screenshot failed:', error);
    res.status(500).json({ error: error.message });
  }
});
Enter fullscreen mode Exit fullscreen mode

That's 35 lines of real, production-ready code. Compare that to managing Puppeteer.

Feature Comparison

Feature Puppeteer Hosted API
Setup time 30 minutes to 6 hours 5 minutes
Lines of code 200+ (with pools, error handling, etc.) 30–50
Infrastructure You manage Chrome, memory, scaling Handled for you
Cost (10k screenshots/month) $3,500/month $29/month
Device presets Manual (11ty lines of code) Built-in (25+ presets)
PDF generation Extra setup, more memory One parameter
Styled screenshots DIY with extra tools Built-in (frames, backgrounds, themes)
Reliability Depends on your uptime 99.9% uptime SLA
Monitoring You do it Included

When to Use Puppeteer

Use Puppeteer if:

  • You're taking screenshots once per month and can afford downtime
  • You have a small, predictable load (< 100 screenshots/month)
  • You need to screenshot internal applications behind firewalls (API can't reach them)
  • You're learning about browser automation (educational context)
  • You have DevOps infrastructure already and want full control

When to Use an API

Use an API if:

  • You want screenshots in production without infrastructure headaches
  • You need reliability and uptime guarantees
  • You want features like device presets, PDF generation, or styled screenshots
  • You're scaling (100+ screenshots/month)
  • You want to focus on your app, not on browser management

Real-World Example: Building a Link Preview Service

You're building a service that generates preview cards for shared links (like Twitter/Discord do).

With Puppeteer:

  1. Deploy to a server with enough RAM for concurrent Chrome processes
  2. Build a request queue to manage browser pools
  3. Handle crashes, memory leaks, and zombie processes
  4. Monitor OOM (out of memory) errors
  5. Scale horizontally (more servers = more complexity)
  6. Cost: $1,000–5,000/month in infrastructure

With an API:

  1. Call the API endpoint
  2. Get screenshot + metadata
  3. Generate preview card
  4. Return to user
  5. Cost: $29/month

The API approach takes one week. The Puppeteer approach takes one month (including operational overhead).

Code Example: Link Preview Service

const express = require('express');
const fetch = require('node-fetch');

const app = express();

app.post('/api/preview', async (req, res) => {
  const { url } = req.body;

  try {
    // Step 1: Take screenshot
    const screenshotResponse = await fetch('https://api.pagebolt.io/api/v1/screenshot', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.PAGEBOLT_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        url,
        width: 1200,
        height: 630,
        blockAds: true,
        blockBanners: true
      })
    });

    if (!screenshotResponse.ok) {
      throw new Error('Screenshot failed');
    }

    // Step 2: Get page metadata (if using inspect endpoint)
    const metadataResponse = await fetch('https://api.pagebolt.io/api/v1/inspect', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.PAGEBOLT_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({ url })
    });

    const metadata = await metadataResponse.json();

    // Step 3: Return preview card
    res.json({
      title: metadata.title || 'Untitled',
      description: metadata.description || '',
      image: screenshotResponse.url,  // Returns CDN URL
      url
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

app.listen(3000);
Enter fullscreen mode Exit fullscreen mode

That's everything. No browser management. No memory issues. No ops overhead.

Hybrid Approach

Some teams use both:

  • Puppeteer for one-off, internal tools (admin dashboards, report generation)
  • API for customer-facing features (link previews, screenshot galleries, monitoring)

This gives you the best of both worlds: control where it matters, simplicity where it doesn't.

The Bottom Line

Puppeteer is powerful if you need it. But most teams don't. They need screenshots, and they need them to work reliably without becoming DevOps engineers.

An API costs $29/month and takes 5 minutes to integrate. Puppeteer costs $3,500+/month and 6 weeks to get right.

The choice depends on your constraints. But for most use cases, the API wins on simplicity, cost, and reliability.


Try PageBolt Free

100 requests/month. No credit card. No setup required.

Start your free trial and see how simple screenshots can be.

Top comments (0)