ScreenshotNeo

BlogHow-to

Set Device Scale Factor for Sharp Puppeteer Screenshots

Set Puppeteer's device scale factor before navigation for sharper screenshots, then use Sharp when you need specific output dimensions.

By the ScreenshotNeo team4 October 20268 min read

Set deviceScaleFactor in Puppeteer’s page.setViewport() before navigating, then capture with page.screenshot(). This controls the device scale used while the browser renders the page. Sharp works at a later stage: use its resize() API to change the captured bitmap’s dimensions, fit, or crop.

const puppeteer = require('puppeteer');

async function main() {
  const url = 'https://example.com';
  const browser = await puppeteer.launch();

  try {
    const page = await browser.newPage();
    await page.setViewport({
      width: 640,
      height: 480,
      deviceScaleFactor: 2,
    });
    await page.goto(url, { waitUntil: 'networkidle2' });

    const screenshot = await page.screenshot({ type: 'png' });
    require('node:fs').writeFileSync('screenshot.png', screenshot);
  } finally {
    await browser.close();
  }
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Puppeteer documents the default device scale factor as 1. A factor of 2 emulates a higher device-pixel ratio. The pixel dimensions will generally scale with that setting for a viewport screenshot; treat that as an expected consequence of DPR emulation, not a guarantee for every capture mode or browser configuration. If exact dimensions matter, inspect the resulting image metadata. [Puppeteer viewport API; Puppeteer screenshot API]

1. Set the viewport before navigation

Configure width, height, and deviceScaleFactor before calling page.goto(). Puppeteer resizes the page when the viewport changes, and its documentation advises setting it before navigation because some sites do not expect their viewport to change after loading. Changes to mobile or touch emulation can also cause a reload. [Puppeteer setViewport API]

The viewport dimensions are CSS pixels. deviceScaleFactor sets the emulated device scale factor, commonly described as device pixel ratio (DPR). A larger factor asks the browser to render at a higher density. It does not set an image’s DPI metadata or prescribe a particular final file size. [Puppeteer viewport API]

Reusable helper with explicit capture settings

const puppeteer = require('puppeteer');
const fs = require('node:fs/promises');

async function capture({
  url,
  path = 'page.png',
  width = 1280,
  height = 800,
  deviceScaleFactor = 2,
  fullPage = false,
}) {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    await page.setViewport({ width, height, deviceScaleFactor });
    await page.goto(url, { waitUntil: 'networkidle2', timeout: 60_000 });
    await page.screenshot({ path, type: 'png', fullPage });
  } finally {
    await browser.close();
  }
}

capture({ url: 'https://example.com' }).catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Use a normal viewport capture when you want the visible area. Set fullPage: true when you need the full document, or provide a clip rectangle when you need a specific region. These change the captured area; they are independent of the DPR setting. For exact dimensions, inspect the saved file because full-page height, clipping, and browser behavior affect the output. [Puppeteer screenshot options]

2. Choose the right capture and image options

Need Use What it controls
Higher-density browser render deviceScaleFactor in setViewport() Emulated device metrics during rendering
Visible viewport only page.screenshot() defaults The currently visible page area
Entire document fullPage: true Capture area, including content outside the viewport
Specific rectangle clip: { x, y, width, height } Capture area in page coordinates
Exact output bounds or smaller output Sharp resize() after capture Post-capture pixel dimensions and fit behavior
File encoding type and, where supported, quality PNG, JPEG, or WebP encoding; quality is for lossy formats

Puppeteer’s screenshot options also include transparent backgrounds and element screenshots. Element screenshots use the element handle’s screenshot method; Puppeteer’s guide says it attempts to scroll a hidden element into view. A quality setting does not apply to PNG. [Puppeteer screenshot guide; Screenshot options API]

Important screenshot settings

// Full page
await page.screenshot({ path: 'full.png', type: 'png', fullPage: true });

// A clipped rectangle
await page.screenshot({
  path: 'region.png',
  type: 'png',
  clip: { x: 0, y: 0, width: 640, height: 400 },
});

// JPEG with lossy quality (quality does not apply to PNG)
await page.screenshot({ path: 'page.jpg', type: 'jpeg', quality: 85 });

// Transparent background, where the page and output format permit it
await page.screenshot({ path: 'transparent.png', omitBackground: true });

3. Use Sharp for post-capture dimensions

If the browser render is already right but the deliverable needs fixed pixel bounds, resize the screenshot with Sharp. Sharp does not configure Puppeteer’s DPR: it changes the bitmap after capture. Select a fit mode intentionally so the result is not unexpectedly cropped, padded, or stretched. [Sharp resize API]

const puppeteer = require('puppeteer');
const sharp = require('sharp');
const fs = require('node:fs/promises');

async function main() {
  const browser = await puppeteer.launch();
  try {
    const page = await browser.newPage();
    await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 2 });
    await page.goto('https://example.com', { waitUntil: 'networkidle2' });

    const screenshot = await page.screenshot({ type: 'png' });
    const output = await sharp(screenshot)
      .resize({ width: 1200, height: 900, fit: 'inside' })
      .png()
      .toBuffer();

    await fs.writeFile('resized.png', output);
    const metadata = await sharp(output).metadata();
    console.log({ width: metadata.width, height: metadata.height });
  } finally {
    await browser.close();
  }
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
Sharp fit Behavior Use when
cover Preserves aspect ratio and crops to fill the target The target must be completely filled
contain Preserves aspect ratio and fits inside, leaving padding as needed The full image must remain visible in exact bounds
fill Stretches to the exact target dimensions Distortion is acceptable or intended
inside Fits within maximum dimensions without cropping Neither dimension may exceed its bound; output can be smaller
outside Resizes until both dimensions meet or exceed the target A minimum dimension is required and later cropping is acceptable

Sharp also exposes image metadata, including pixel width and height, so you can verify the actual output. Its density option concerns image metadata; use resize() to change pixel dimensions. [Sharp input and metadata API; Sharp output API]

4. cURL, Python, and Node.js alternatives

The title’s implementation is Node.js Puppeteer. cURL and Python do not set Puppeteer’s viewport directly; they can call a screenshot service that performs browser capture. The following ScreenshotNeo examples use its documented API endpoint and the same target URL. See the ScreenshotNeo API documentation for request options.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://example.com \
  -o shot.webp

Python

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
    timeout=90,
)
r.raise_for_status()
with open("shot.webp", "wb") as f:
    f.write(r.content)

Node.js service call

const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const bytes = Buffer.from(await res.arrayBuffer());
require('node:fs').writeFileSync('shot.webp', bytes);

5. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. One GET request returns a PNG, JPEG, WebP, or PDF. Its capture can accept cookie or consent banners like a visitor and remove 60+ known consent platforms, newsletter popups, and chat widgets before the shot; each of those steps can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and whether the request was billed.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://example.com \
  -o shot.webp

ScreenshotNeo also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan. See the API docs for configuration and sign up for 1,000 free screenshots a month, with no card required.

6. Troubleshooting

Symptom Likely cause Fix
Screenshot looks no sharper The source page may not contain higher-resolution assets, or the screenshot is being viewed scaled down. Confirm the viewport has the intended deviceScaleFactor before navigation; inspect source pixel dimensions and view at native size.
Output dimensions are unexpected fullPage, clip, a later Sharp resize, or the page’s document height changes the capture bounds. Inspect image metadata; check CSS viewport dimensions, DPR, screenshot area options, then any post-capture transforms.
Viewport change causes layout differences The site responds to viewport changes; mobile/touch settings can also trigger reloads. Set the viewport and emulation options before navigation, then wait for the page state you intend to capture.
Capture times out waiting for navigation The site may keep network connections open, so a network-idle condition may not arrive. Choose a navigation wait condition appropriate to the page, or wait for a specific selector or bounded delay after navigation.
JPEG screenshot rejects quality or looks different Quality applies to lossy formats, not PNG. Use JPEG or WebP when lossy compression is acceptable; keep PNG when you need lossless output.
Sharp output appears cropped or padded The selected fit mode changes how aspect ratios are reconciled. Choose inside or contain to preserve the whole image, cover to crop, or fill to stretch.
DPI metadata changed but pixel size did not Density metadata is separate from bitmap resizing. Use Sharp resize() for dimensions and inspect width/height metadata afterward.

7. Performance, reliability, and cost

  • Render cost: A higher DPR can require more raster work and a larger bitmap, especially for full-page captures. Keep the factor only as high as the output needs; no universal speed or memory multiplier should be assumed.
  • Output size: PNG is lossless and can be larger than lossy formats. JPEG or WebP can reduce transfer/storage size when their compression artifacts are acceptable.
  • Reliability: Set viewport and emulation before navigation, give navigation and capture bounded timeouts, and always close the browser in a finally block. For exact deliverables, verify image metadata rather than assuming dimensions.
  • Dependency behavior: Puppeteer and Sharp APIs can evolve. Check the installed package versions and their API docs when relying on version-specific options.
  • Hosted capture cost: ScreenshotNeo’s Free tier is 1,000 shots/month without a card; paid plans are Starter $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000. Yearly billing gives two months free. Only clean shots are billed; verdict and billing headers identify outcomes.

8. FAQ

Does Sharp set Puppeteer’s device scale factor?

No. Puppeteer sets emulated device scale during rendering; Sharp transforms the captured image afterward.

Should I use deviceScaleFactor or Sharp resize for a 1200 by 900 image?

Use DPR to control browser rendering density. Use Sharp resize when the final bitmap must meet specific bounds, and choose a fit mode based on whether cropping, padding, or distortion is acceptable.

Does deviceScaleFactor change CSS viewport width?

No. The viewport width and height are specified in CSS pixels; the scale factor describes emulated device density.

Can I capture only one element?

Yes. Puppeteer supports element screenshots through an element handle. For page screenshots, use a clip rectangle when a coordinate-based region is more appropriate.

Is a higher scale factor always better?

No. It can increase rendering work and image size, and it cannot create detail absent from the page’s assets. Choose a factor that meets the consuming display or output requirements.