Puppeteer Screenshot Is Blurry: How to Set Device Scale Factor
Fix soft Puppeteer screenshots by setting deviceScaleFactor before navigation. Learn how viewport pixels, screenshot options, and source images affect sharpness.
Direct answer: set deviceScaleFactor in Puppeteer’s viewport before navigating, then capture the page with page.screenshot(). A value above 1 asks Chromium to render more device pixels for the same CSS viewport. It can make raster screenshots look sharper at their intended display size, but it cannot restore detail missing from source images or prevent later downscaling.
Set deviceScaleFactor before navigation
Here is a complete Node.js example. It launches Chromium, sets the viewport, navigates to a page, waits for the load event, and saves a PNG:
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setViewport({
width: 1280,
height: 800,
deviceScaleFactor: 2,
});
await page.goto('https://example.com', {
waitUntil: 'load',
timeout: 30000,
});
await page.screenshot({ path: 'page.png', type: 'png' });
} finally {
await browser.close();
}
})();
The official Puppeteer Page.setViewport() API recommends setting the viewport before navigation. Changing viewport properties can cause a page reload in some cases, so configure it up front. The Puppeteer screenshot guide covers page and element screenshots.
Understand CSS pixels and device pixels
width and height define the emulated viewport in CSS pixels. deviceScaleFactor sets the emulated device pixel ratio used for rendering. At a factor of 2, a 1280 × 800 CSS viewport can produce an image with roughly twice as many pixels in each dimension, subject to screenshot mode and output. That means about four times as many image pixels as a factor of 1.
The larger bitmap may look crisper when displayed at the corresponding size, but it also takes more memory and can increase encoding time and file size. If an image viewer scales the output to fit a smaller window, or your application later resizes it down, the perceived benefit may be reduced.
| Setting | Controls | Use it for |
|---|---|---|
viewport.width / height |
Emulated layout viewport, in CSS pixels | Controlling responsive page layout and visible area |
viewport.deviceScaleFactor |
Emulated device pixel ratio | Rendering the same CSS viewport at higher pixel density |
screenshot.clip.scale |
Scale for a clipped screenshot | Adjusting a clip capture; this is separate from the viewport device scale factor |
screenshot.quality |
Quality for supported lossy image output | JPEG/WebP quality; it does not apply to PNG |
Choose a screenshot format and capture mode
Use PNG while diagnosing softness because it avoids JPEG compression artifacts. Puppeteer also supports screenshot options such as fullPage, clip, type, and quality. Quality does not control PNG sharpness: changing it will not sharpen a PNG. For a clipped capture, check the clip coordinates and dimensions, and keep its scale setting distinct from deviceScaleFactor.
// Full-page PNG
await page.screenshot({ path: 'full-page.png', fullPage: true, type: 'png' });
// Clip a CSS-pixel rectangle from the page
await page.screenshot({
path: 'region.png',
type: 'png',
clip: { x: 0, y: 0, width: 640, height: 400, scale: 1 },
});
For a specific element, use an element handle’s screenshot method:
const element = await page.$('#report');
if (!element) throw new Error('Element #report was not found');
await element.screenshot({ path: 'report.png', type: 'png' });
See the official ScreenshotOptions reference and ScreenshotClip reference for the option details and current types.
Diagnose remaining blur
- Set the viewport before
goto(). Include width, height, and deviceScaleFactor in the same call. - Save a PNG. This removes JPEG compression as one possible source of softness.
- Inspect actual pixel dimensions. Compare the file’s dimensions with the CSS viewport and scale factor instead of relying on how a viewer fits it on screen.
- Check capture geometry. If using
clip, verify x, y, width, and height and remember that clip scale is a separate option. - Inspect the page itself. Low-resolution source images, CSS transforms, browser zoom, font rasterization, and later image resizing can affect perceived sharpness. Raising device scale factor does not fix every cause.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Output is still the same apparent resolution | The viewport setting was omitted, applied after navigation, or the image is being displayed scaled down. | Set deviceScaleFactor before navigation and inspect the saved file’s pixel dimensions. |
Changing quality does not improve PNG |
Puppeteer’s screenshot quality option does not apply to PNG. | Keep PNG for lossless diagnosis; investigate rendering, source assets, and resizing. |
| Changing clip scale has no effect on the whole page | clip.scale applies to a clipped screenshot, not the viewport’s device pixel ratio. |
Set deviceScaleFactor through page.setViewport(). |
| Text or images look soft despite higher pixel count | The source asset may be low resolution, the page may use transforms, or the result may be downscaled after capture. | Inspect the page and downstream image pipeline; use a higher-resolution source asset where available. |
| Page layout changes after setting the viewport | Viewport dimensions affect responsive layout; viewport updates can also reload a page in some cases. | Set the intended viewport before navigation and choose CSS dimensions that match the layout you need. |
| Screenshot call times out or navigation fails | The page may not reach the selected wait condition within the timeout, or the site may be unavailable. | Handle navigation errors explicitly, select an appropriate wait condition, and set a bounded timeout. Check that the target is reachable from the capture environment. |
Performance, reliability, and cost considerations
A higher device scale factor increases output pixel count quickly: doubling the factor gives approximately four times the pixels for the same CSS viewport. That can increase memory use, image encoding work, transfer size, and storage. Use the smallest factor that meets the display requirement, especially for full-page captures, where tall pages already create large images.
For repeatable captures, set the viewport before navigation, use an explicit screenshot format, and choose a navigation wait condition that fits the page. A successful navigation event does not guarantee every lazy image or late-loading widget is ready; if those matter, wait for the relevant selector or page state before capture. Keep timeouts bounded and treat navigation or screenshot errors as failures to retry deliberately rather than saving an assumed-valid image.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF. The API supports device presets, custom viewports, and retina scale, alongside full-page and element captures. 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,
)
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())));
- Cookie and consent banners are accepted and removed before capture, along with known consent platforms, newsletter popups, and chat widgets. Each cleanup step can be turned off.
- Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing; response headers report the page verdict and billing status.
- An MCP server provides
take_screenshot,get_page_info, andcapture_pdftools for AI agents and MCP clients. - 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 to get 1,000 screenshots a month with no card.
FAQ
Will deviceScaleFactor make every screenshot sharper?
No. It changes rendering pixel density. It cannot add detail to a low-resolution source image or undo later downscaling.
Should I set it to 2 or 3?
Choose based on the output size you need and the cost of larger images. Start with the lowest factor that meets the display requirement, then inspect the saved dimensions and appearance.
Does the setting change responsive layout?
The CSS viewport width and height drive responsive layout. Device scale factor changes pixel density; set both deliberately before navigating.


