Stop maintaining your own Puppeteer setup. There is a simpler way.
If you have a Puppeteer instance running in production, you know the maintenance tax.
Chromium updates break things on a schedule you do not control. Memory leaks accumulate until you are restarting the process on a cron job. Concurrency requires you to either queue jobs yourself or spin up more instances and manage a pool. And all of this is running on infrastructure you are paying for, for a feature that is almost certainly not your core product.
I have seen this pattern dozens of times. Someone needs screenshots for their app. They set up Puppeteer. It works locally. It mostly works in production. Six months later, they are debugging a timeout issue at 2am for a feature that generates social cards.
The real cost is not the server. It is the hours.
The patchwork alternative is not much better
The usual escape hatch is to pay for multiple SaaS tools. A screenshot service here, a PDF generator there, an OG image tool somewhere else. Now you have three API keys, three billing accounts, three sets of docs, and three potential points of failure.
You also pay three times for what is, under the hood, the same thing: a managed Chromium instance.
One API for all of it
PageBolt is a managed web capture API that replaces the whole stack. One key, one billing meter, seven tools:
- Screenshots
- PDFs
- OG images
- Video recordings
- Multi-step browser sequences
- Page inspection
- Audio Guide (AI voice narration on videos)
Here is how each one works in practice.
Screenshots from a URL
curl -X POST https://api.pagebolt.dev/v1/screenshot \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com",
"fullPage": true,
"blockBanners": true,
"blockAds": true,
"blockChats": true
}' \
--output screenshot.png
The blockBanners flag removes GDPR cookie consent popups. blockAds removes ad slots. blockChats removes Intercom and similar widgets. You get a clean screenshot without any manual CSS overrides.
In Node.js:
const response = await fetch('https://api.pagebolt.dev/v1/screenshot', {
method: 'POST',
headers: {
'x-api-key: YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: 'https://yourapp.com',
fullPage: true,
blockBanners: true,
blockAds: true,
}),
});
const buffer = await response.arrayBuffer();
fs.writeFileSync('screenshot.png', Buffer.from(buffer));
Screenshots from HTML
You do not need a live URL. You can send raw HTML directly and get a screenshot back. This is useful for rendering email templates, receipts, or any dynamically generated content.
const html = `
<html>
<body style="font-family: sans-serif; padding: 40px; background: #f5f5f5;">
<h1>Invoice #1042</h1>
<p>Total: $249.00</p>
</body>
</html>
`;
const response = await fetch('https://api.pagebolt.dev/v1/screenshot', {
method: 'POST',
headers: {
'x-api-key: YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({ html, format: 'jpeg', quality: 90 }),
});
PDF generation
Same pattern, different endpoint. Works from a URL or from HTML.
curl -X POST https://api.pagebolt.dev/v1/pdf \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourapp.com/reports/q4-2025",
"format": "A4",
"margin": { "top": "20mm", "bottom": "20mm", "left": "15mm", "right": "15mm" }
}' \
--output report.pdf
From HTML:
const response = await fetch('https://api.pagebolt.dev/v1/pdf', {
method: 'POST',
headers: {
'x-api-key: YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
html: '<h1>Monthly Report</h1><p>Revenue: $12,400</p>',
format: 'Letter',
}),
});
const buffer = await response.arrayBuffer();
fs.writeFileSync('report.pdf', Buffer.from(buffer));
OG image generation
There are three built-in templates. You pass in your title, description, and logo, and you get a properly sized social card back. No Figma, no image editing, no custom renderer.
const response = await fetch('https://api.pagebolt.dev/v1/og-image', {
method: 'POST',
headers: {
'x-api-key: YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
template: 'modern',
title: 'How to deploy Node.js to production',
description: 'A step-by-step guide',
logo: 'https://yoursite.com/logo.png',
}),
});
On the Growth tier, you can pass in fully custom HTML instead of using a template, which means your OG images can look exactly like your brand.
Multi-step browser sequences
This is where the API earns its keep compared to a simple screenshot tool. You can define a sequence of browser actions, just like you would with Puppeteer or Playwright, and get screenshots or PDFs at specific points in the flow.
Here is an example that logs into a dashboard and captures a screenshot after login:
const response = await fetch('https://api.pagebolt.dev/v1/sequence', {
method: 'POST',
headers: {
'x-api-key: YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
steps: [
{ action: 'navigate', url: 'https://app.example.com/login' },
{ action: 'fill', selector: '#email', value: 'user@example.com' },
{ action: 'fill', selector: '#password', value: 'password123' },
{ action: 'click', selector: '#login-button' },
{ action: 'wait_for', selector: '.dashboard-header' },
{
action: 'screenshot',
fullPage: false,
blockBanners: true,
},
],
}),
});
The response includes each screenshot output in order. No Puppeteer session, no browser pool, no cleanup. You POST, you get files.
Clean capture features
A few flags worth knowing:
| Flag | What it does |
|---|---|
blockBanners: true |
Removes GDPR cookie consent popups |
blockAds: true |
Removes ad slots and tracking pixels |
blockChats: true |
Removes Intercom, Crisp, Drift, and similar widgets |
blockTrackers: true |
Blocks analytics scripts |
darkMode: true |
Emulates prefers-color-scheme: dark
|
viewportDevice: "iphone_14_pro" |
Emulates a specific device |
fullPage: true |
Captures the entire scrollable page |
fullPageScroll: true |
Triggers lazy-loaded images before capture |
There are 25+ device presets. You can also pass custom viewport dimensions if none of the presets match.
Pricing
- Free: 100 requests/month, no credit card required
- Starter: $29/month for 5,000 requests
- Growth: $79/month for 25,000 requests
- Scale: $199/month for 100,000 requests
Paid plans include a 14-day free trial.
The free tier is real. It does not expire after a week. If you are evaluating, start there: pagebolt.dev.
The actual tradeoff
The main objection I hear is "I want full control over Chromium." That is a reasonable objection for maybe 5% of use cases, where you need extremely custom browser behavior.
For everyone else, the tradeoff is clear. You hand off Chromium maintenance, concurrency management, and infrastructure scaling, and you get those hours back to work on your actual product.
If you are running Puppeteer in production today, the free tier takes about 10 minutes to evaluate. The migration is usually less than an hour.
Top comments (0)