Puppeteer Screenshot Is Blurry Only When Saved as JPEG
If a Puppeteer screenshot looks blurry only as JPEG, compare it with PNG at identical pixel dimensions before changing quality or capture settings.
If a Puppeteer screenshot is sharp as PNG but blurry as JPEG, first compare both files at the same native pixel dimensions and with identical capture options. If the dimensions match, JPEG encoding or its quality setting is a plausible cause. If the JPEG has fewer pixels, investigate the viewport, device scale factor, clipping, full-page setting, or later resizing before changing quality. The title alone does not establish the cause.
Puppeteer supports PNG, JPEG, and WebP screenshots. Its screenshot quality option accepts values from 0 to 100 and does not apply to PNG. Set the format explicitly while diagnosing so the filename or a default cannot obscure which encoding was requested. Puppeteer ScreenshotOptions · Supported image formats
Diagnose the blur with a controlled comparison
- Save a PNG control image. Record its actual pixel width and height from the resulting file.
- Save a JPEG with an explicit
typeand a deliberatequalityvalue. Keep every other capture option unchanged. - Compare both files at their native pixel dimensions. Avoid comparing a resized preview or a copy that has passed through a content system or messaging app.
- If dimensions differ, fix the capture or downstream scaling difference first. If dimensions match and PNG is crisp while JPEG is soft, try a higher JPEG quality and decide whether the resulting appearance is worth its file size.
- Inspect the saved files themselves, not only their extensions. When a path is provided, Puppeteer can infer the screenshot type from its extension; explicit
typeremoves ambiguity.
This procedure isolates likely causes; it is not a claim that a particular site or code path has been tested. To identify a specific root cause, you need the screenshot call, Puppeteer and browser versions, capture dimensions, quality setting, saved file dimensions, and any later image processing.
Runnable Node.js example: save PNG and JPEG
Install Puppeteer in a Node.js project with npm install puppeteer. Save this as compare-screenshots.js and run node compare-screenshots.js https://example.com. The script captures both formats using the same viewport and device scale factor. Adjust quality to compare deliberate JPEG settings; it has no effect on PNG.
const puppeteer = require('puppeteer');
async function main() {
const url = process.argv[2] || 'https://example.com';
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage({
viewport: { width: 1280, height: 800 },
deviceScaleFactor: 1,
});
await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000 });
await page.screenshot({ path: 'capture.png', type: 'png' });
await page.screenshot({
path: 'capture.jpg',
type: 'jpeg',
quality: 90,
});
} finally {
await browser.close();
}
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
The 90 above is an example comparison value, not a universal recommendation. Try values suited to your image and file-size needs. A page that never reaches the selected navigation condition may time out; choose a wait condition that fits the site and explicitly wait for a meaningful selector when needed. See the Page.screenshot() reference and Page.viewport() reference.
Check dimensions and capture settings
CSS viewport dimensions and output image dimensions are related but are not interchangeable. Device scale factor affects the number of device pixels produced for CSS pixels. A clip can restrict the captured region, and a full-page capture changes the captured page area. Keep these settings identical in a format comparison.
page.viewport() reports the viewport settings configured through page.setViewport() or the connection’s default viewport. Puppeteer’s reference cautions that this method does not inspect the actual rendered viewport. Use the saved image dimensions to verify the output rather than treating page.viewport() as file metadata.
const configuredViewport = page.viewport();
console.log('Configured viewport:', configuredViewport);
const png = await page.screenshot({ type: 'png' });
console.log('PNG bytes:', png.byteLength);
The screenshot method returns image data as a Uint8Array by default (or a string when base64 encoding is requested). The byte length is not the pixel width or height. Inspect the saved image with an image metadata tool or viewer that reports native dimensions.
Choose an output format for the job
| Format | Use in diagnosis | Relevant caveat |
|---|---|---|
| PNG | Useful control when checking whether JPEG encoding contributes to softness. | The documented JPEG quality option does not apply to PNG. The cited Puppeteer references do not promise a particular file size. |
| JPEG | Useful when the workflow requires JPEG output or you are comparing its quality setting. | Set quality explicitly for a controlled comparison. Judge fine text and edges at native dimensions. |
| WebP | A supported alternative to evaluate in your own delivery pipeline. | The cited references establish support, not comparative quality or size benchmarks. |
There is no single best format for every page. Compare the actual output at its native dimensions, and weigh sharpness, file size, and the importance of fine text and edges. Do not infer that WebP will be sharper or smaller for your page without checking your output.
Common causes and fixes
| Symptom | Likely area to inspect | Next step |
|---|---|---|
| PNG is crisp; JPEG is soft; dimensions match. | JPEG encoding or quality. | Raise JPEG quality and compare the native files. Keep the format explicit. |
| JPEG dimensions are smaller than PNG. | Different viewport, device scale factor, clip, full-page setting, or later resize. | Match capture options and inspect any processing after Puppeteer saves the image. |
| Both formats look soft. | Capture resolution, source content, viewer scaling, or downstream processing. | Check the saved dimensions, inspect the original page assets, and compare at native size. |
| Saved file format does not match expectation. | Type inferred from the path extension or omitted type. | Set type: 'jpeg' or type: 'png' explicitly and check the actual file. |
| Text or images look soft in both outputs. | Source rendering or asset resolution. | Inspect the page at the same viewport in a browser and check whether source images are already scaled up. |
| Navigation times out before capture. | The chosen navigation wait condition may not be reached. | Choose a suitable condition for the page, use an appropriate timeout, or wait for a page-specific selector. |
Reliability, speed, and cost considerations
Capturing two formats means asking the browser to produce two output files, so it adds capture work and storage compared with producing one. PNG, JPEG, and WebP output sizes depend on the page and the encoding; the cited API references provide no file-size or speed benchmarks. Measure the files and runtime in your own workload before setting a pipeline policy.
For repeatable results, pin the viewport, device scale factor, capture region, full-page setting, browser and Puppeteer versions, and any image-processing steps. Use explicit output types and retain a representative control image when changing capture code. Puppeteer’s system requirements can change, so consult the current system requirements guide when browser launch or environment compatibility is part of the problem.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request returns an image or PDF, and its screenshot options include format, viewport, device scale factor, and full-page capture. The same parameter names used by other screenshot APIs also work, which can make switching straightforward. 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,
)
r.raise_for_status()
with open("shot.webp", "wb") as output:
output.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 require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
- Cookie and consent banners are accepted like a visitor; more than 60 known consent platforms, newsletter popups, and chat widgets are removed before the shot. Each step can be turned off.
- Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing. Response headers say which page verdict applied and whether the request was billed.
- An MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs.
- The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots; every feature is on every plan.
Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.
FAQ
Does setting JPEG quality to 100 guarantee a sharp screenshot?
No. Quality only controls JPEG encoding; it cannot add pixels missing from the capture or undo later resizing. Compare dimensions and the saved files.
Does PNG support Puppeteer’s screenshot quality option?
No. The documented quality range is 0–100 and does not apply to PNG.
Does Puppeteer support WebP screenshots?
Yes. The documented format names are PNG, JPEG, and WebP. The references cited here do not establish which format will look best or produce the smallest file for your page.
What information is needed to pinpoint my case?
Share the screenshot options, Puppeteer and browser versions, viewport and device scale factor, both files’ pixel dimensions, JPEG quality, and any resizing or recompression after capture.


