Puppeteer Screenshot Is Blurry Only on High-DPI Screens: What to Check
A blurry Puppeteer screenshot on a high-DPI display can come from scale, capture geometry, encoding, or later resizing. Compare CSS viewport size with the saved image before changing settings.
A Puppeteer screenshot that looks blurry on a high-DPI display does not, by itself, prove a Puppeteer defect. First compare the page viewport in CSS pixels with the saved image’s raster dimensions. Then verify the effective deviceScaleFactor, the capture region and options, the image format, and whether anything resizes the image after capture.
Puppeteer documents deviceScaleFactor with a default of 1. If you need more raster pixels for a given CSS viewport, set the scale factor explicitly and inspect the output dimensions. A higher factor increases the raster dimensions; it does not guarantee that every page asset or later display step will look sharp.
1. Understand CSS pixels and screenshot pixels
viewport.width and viewport.height describe the page viewport in CSS pixels. The saved file has raster dimensions in image pixels. Do not assume those measurements are equal: inspect the image itself and record the scale factor used for the capture. Puppeteer’s viewport interface documents the scale factor and its default. Puppeteer viewport reference.
For a straightforward viewport screenshot, the expected raster dimensions are generally related to the CSS viewport and effective device scale factor. Treat that as a diagnostic expectation, not a substitute for checking the actual file: full-page capture, clipping, capture-beyond-viewport behavior, and the page’s content can affect the resulting region or dimensions.
| What you requested | What to inspect | What it can tell you |
|---|---|---|
| CSS viewport width and height | The exact values passed to setViewport() |
Whether the page was laid out at the intended size |
deviceScaleFactor |
The value explicitly set in the viewport configuration | Whether capture was configured for a denser raster |
| Saved image | Actual pixel width and height, plus format | Whether the output dimensions match expectations |
| Capture geometry | fullPage, clip, and captureBeyondViewport |
Whether the captured area differs from the viewport |
2. Reproduce the capture with explicit settings
Reduce the problem to one URL, an explicit viewport, one screenshot call, and the exact Puppeteer and Chrome versions. The following Node.js example records the versions and viewport configuration, saves a PNG, and prints the file’s dimensions so they can be compared with the request.
import puppeteer from 'puppeteer';
import { readFile } from 'node:fs/promises';
const url = process.argv[2] ?? 'https://example.com';
const output = 'capture.png';
const viewport = {
width: 1280,
height: 800,
deviceScaleFactor: 2,
};
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.setViewport(viewport);
await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000 });
await page.screenshot({ path: output, type: 'png' });
const packageVersion =
(await import('puppeteer/package.json', { with: { type: 'json' } })).default.version;
const browserVersion = await browser.version();
const image = readPngDimensions(await readFile(output));
console.log({
url,
packageVersion,
browserVersion,
viewport,
screenshot: { path: output, type: 'png', ...image },
});
} finally {
await browser.close();
}
function readPngDimensions(buffer) {
const signature = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
if (buffer.length < 24 || !buffer.subarray(0, 8).equals(signature)) {
throw new Error('Output is not a valid PNG');
}
return {
width: buffer.readUInt32BE(16),
height: buffer.readUInt32BE(20),
};
}
Run it with node capture.mjs https://your-page.example in a project where puppeteer is installed. If your Node.js/Puppeteer version does not support importing the package JSON as shown, log the package version from your lockfile or package metadata instead. The browser version is reported by Puppeteer at runtime.
The PNG dimension reader above only checks the PNG header, which is enough to report its width and height. For other formats or more detailed image inspection, use an image tool appropriate to your environment. Puppeteer’s screenshot guide documents page and element screenshot workflows. Puppeteer screenshots guide.
3. Check the settings that change output
Set and verify deviceScaleFactor
Set deviceScaleFactor explicitly in page.setViewport() instead of relying on a default or assuming the host machine’s display setting was applied. The documented default is 1. A factor of 2 requests a denser device scale for the page; inspect the resulting image dimensions to confirm the capture produced the raster size you expected.
Do not mix up the physical display’s scaling with the viewport configuration in your script. The key evidence is the value Puppeteer used for that page and the dimensions of the saved file.
Record the screenshot region and geometry
Start with a simple viewport screenshot. Then add options one at a time. Record whether the capture uses:
fullPageto capture the full page rather than only the current viewport;clipto capture a specified region; orcaptureBeyondViewportto control capture outside the viewport.
These options affect what is captured and may change the output dimensions. When diagnosing blur, compare captures with the same geometry and inspect the image size. See Puppeteer’s ScreenshotOptions reference and Page.screenshot() reference.
Separate resolution from encoding
Save a PNG first. Then, if compression softness is suspected, capture JPEG with an explicit quality value and compare at the same display size. Puppeteer’s screenshot quality option applies to JPEG and WebP, not PNG. Changing quality does not add raster pixels; it changes encoding. The options and supported formats are described in the ScreenshotOptions reference.
Also check the viewer or downstream pipeline. An image can have adequate dimensions but appear soft if a page, editor, or preview scales it to a non-native display size or if another step resizes or recompresses it.
Check page content
A dense screenshot cannot restore detail that is absent from the page asset. If text, logos, or images look soft while the screenshot dimensions are correct, check the page’s own source assets and styles at the chosen viewport. Compare the same page at the same CSS viewport before attributing the result to screenshot capture.
4. Capture variations to isolate the cause
- Capture a PNG with one explicit viewport and explicit scale factor.
- Record the file’s raster width and height.
- Repeat with a scale factor of
1and compare dimensions and sharpness at the same on-screen display size. - Keep the viewport fixed and compare viewport capture with
fullPageonly if the original uses it. - If relevant, compare a simple viewport capture with the original
cliporcaptureBeyondViewportconfiguration. - Compare PNG against JPEG/WebP only after confirming the dimensions; use format-appropriate quality settings.
- Record exact Puppeteer and Chrome versions for every reproduction.
Change one variable per capture. Otherwise, a difference in dimensions, region, or compression can obscure which setting mattered.
5. Troubleshoot common symptoms
| Symptom | Likely explanation to check | Next step |
|---|---|---|
| Image dimensions match the CSS viewport but look too small or soft on a dense display | The effective scale factor may be 1 |
Set deviceScaleFactor explicitly, capture again, and inspect raster dimensions |
| Dimensions differ from the viewport in an unexpected way | Full-page capture, clipping, or capture beyond the viewport changes the captured region | Log capture options and reproduce first with a plain viewport screenshot |
| PNG looks crisp but JPEG looks soft | Lossy encoding or the chosen JPEG quality may be affecting detail | Use PNG when lossless output is needed, or adjust JPEG quality and compare at identical display size |
| PNG still looks soft despite sufficient pixel dimensions | The display or a later processing step may be resizing the file; page assets may also lack detail | Inspect the original file at native size and check any resize, preview, upload, or recompression step |
| Changing the display’s DPI does not change the screenshot | The script’s viewport configuration, rather than the display setting, determines the relevant capture setup | Log the viewport passed to Puppeteer and explicitly set its scale factor |
| Only one Puppeteer/Chrome combination reproduces the issue | The result may be version-specific | Include both exact versions, the minimal script, output dimensions, and options in a bug report |
A Puppeteer issue discussion titled “captureBeyondViewport does not respect deviceScaleFactor” concerns a specific report; it is context for checking versions and options, not evidence of a general current high-DPI defect. Puppeteer issue #11514.
6. Reliability, performance, and cost considerations
Higher raster dimensions mean more image data to encode, store, move, or process. Full-page captures can produce especially large outputs on long pages. Choose the smallest viewport and scale factor that meet the downstream use case, and avoid capturing a full page when only the visible region is needed. These are practical tradeoffs; the dossier provides no benchmark or fixed performance multiplier.
For reliable diagnosis, pin or record the Puppeteer package and browser versions, use an explicit viewport, keep capture options in the reproduction, and retain the original output file. If a capture is part of a larger pipeline, inspect dimensions both immediately after Puppeteer and after every transform or upload stage.
Self-hosted Puppeteer has no per-screenshot ScreenshotNeo charge, but it still uses your browser runtime and infrastructure. A hosted screenshot API can reduce browser setup and maintenance; compare its billing rules and output controls with your workload before switching.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. For a simple capture:
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)
open("shot.webp", "wb").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 import('node:fs/promises').then(({ writeFile }) => writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
See the ScreenshotNeo API documentation for request options. Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.
Sign up for ScreenshotNeo and get 1,000 screenshots a month free, with no card.
Frequently asked questions
Does high-DPI screenshot blur mean Puppeteer is broken?
No. The symptom alone cannot establish a defect. Gather a minimal reproduction and the exact browser/library versions before making that claim.
Should I always use a scale factor of 2?
No. Choose a factor based on the raster size your output needs, then verify the saved dimensions and consider the extra image data.
Can screenshot quality sharpen a PNG?
No. Puppeteer’s screenshot quality setting does not apply to PNG, and quality controls encoding rather than adding resolution.
What details should a bug report include?
Include a minimal script, URL or reproducible page, Puppeteer and Chrome versions, explicit viewport and scale factor, screenshot options, output dimensions, and the resulting image format.


