ScreenshotNeo

BlogHow-to

Puppeteer Screenshot with a Fixed Viewport and Device Scale Factor

Set Puppeteer’s viewport before navigation to control CSS dimensions and pixel density. Learn viewport, full-page and clipped captures, output options, and troubleshooting.

By the ScreenshotNeo team4 October 20267 min read

Set the viewport before navigating, then call page.screenshot(). The viewport’s width and height are CSS pixels; deviceScaleFactor sets the device scale used to render the page. These are separate settings: choose dimensions for the layout you want, then choose a scale factor for the output density. There is no universal correct viewport or scale factor.

The example below uses illustrative values of 1280 × 720 CSS pixels and a device scale factor of 2. It saves a viewport screenshot as a PNG. For the whole document, add fullPage: true; for a specific rectangle, use clip.

Complete runnable example

Install Puppeteer in a Node.js project, save this as screenshot.mjs, and run it with node screenshot.mjs. Puppeteer’s viewport API resizes the page, and its screenshot guide uses Page.screenshot() for capture. Choose a readiness condition that fits the site: networkidle2 is used here, but it cannot guarantee that every page-specific animation or dynamic component has finished changing. See the viewport API and screenshots guide.

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();

  // These dimensions are CSS pixels. The scale factor is separate.
  await page.setViewport({
    width: 1280,
    height: 720,
    deviceScaleFactor: 2,
  });

  await page.goto('https://example.com', {
    waitUntil: 'networkidle2',
  });

  await page.screenshot({ path: 'screenshot.png' });
} finally {
  await browser.close();
}

Install the package with npm install puppeteer. The finally block closes the browser even if navigation or capture fails. For a reproducible result, pin the Puppeteer version in your project lockfile and record it alongside the viewport settings; the API and defaults can change between releases.

What the viewport and scale factor control

  • width and height: the viewport dimensions in CSS pixels. They affect responsive breakpoints and the visible portion of the page.
  • deviceScaleFactor: the scale factor used for the viewport’s device metrics. A higher value can produce more image pixels for the same CSS-sized viewport. It does not make the CSS layout wider or taller.
  • Screenshot scope: by default, the screenshot is of the viewport. fullPage: true requests a full-page screenshot, while clip specifies a rectangle. Scope is independent of the viewport setting.

For example, a 1280 × 720 CSS-pixel viewport at scale factor 2 targets a denser rendering than the same viewport at scale factor 1. Do not treat those numbers as a promise about the dimensions of every output: full-page capture and clipping change the captured area, and the page itself can affect the result.

Choose the right capture scope

Viewport screenshot

Omit fullPage to capture the viewport, which is the default:

await page.screenshot({ path: 'viewport.png' });

Full-page screenshot

Set fullPage: true when the image should include the full page rather than just the visible viewport:

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

A full-page screenshot can be much taller and larger than a viewport image. It does not mean the page’s layout viewport has been changed to the full document height. If you need a particular region, prefer an explicit clip.

Capture a rectangular region

Use clip to capture a rectangle, with coordinates and dimensions in CSS pixels. Keep the clip within the area the page can capture.

await page.screenshot({
  path: 'region.png',
  clip: { x: 100, y: 120, width: 600, height: 400 },
});

Pick viewport dimensions, capture scope, and output encoding as separate decisions. The screenshot options reference documents the available screenshot settings.

Match a named device when needed

If the goal is to reproduce a known device profile, use page.emulate(device). Emulation sets the user agent and viewport metrics together. The Puppeteer documentation advises applying emulation before navigation.

import puppeteer from 'puppeteer';
import { KnownDevices } from 'puppeteer';

const browser = await puppeteer.launch();
try {
  const page = await browser.newPage();
  await page.emulate(KnownDevices['iPhone 13']);
  await page.goto('https://example.com', { waitUntil: 'networkidle2' });
  await page.screenshot({ path: 'device.png' });
} finally {
  await browser.close();
}

Use a custom setViewport() when you need exact width, height, and scale values independent of a named device. Use emulation when the device’s user agent and viewport profile are both relevant. See the emulation API.

Output formats and screenshot options

The documented screenshot options include a file path, image type, encoding, quality, and omitBackground. Use the options that match the downstream consumer:

  • PNG: a suitable default when you need a lossless image. quality does not apply to PNG.
  • JPEG: use type: 'jpeg' when a lossy image is acceptable; set quality to control encoding quality.
  • WebP: use type: 'webp' where supported by your Puppeteer and browser setup; quality applies to lossy encoding.
  • Bytes instead of a file: omit path and use the returned screenshot data in your application. The documented encoding option controls whether the returned data is a buffer or base64 string.
  • Transparent background: use omitBackground: true when transparency is required and the page background should be omitted.
const image = await page.screenshot({
  type: 'jpeg',
  quality: 80,
});

For a file, provide a path such as path: 'page.jpg'. Check the current ScreenshotOptions reference for version-specific types and behavior.

Wait for the page state you need

page.goto() can wait for different navigation lifecycle events. The Puppeteer screenshots guide demonstrates waitUntil: 'networkidle2'; it is a useful starting point for pages that need network activity to settle, but it is not proof that all visible content is ready. A page may load content later, keep connections open, or update after navigation.

When a page has a known ready element, wait for it explicitly after navigation:

await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('[data-screenshot-ready]');
await page.screenshot({ path: 'ready.png' });

Replace the selector with an element that indicates the content you need is present. If the visual state depends on a known delay, a short page.waitForTimeout() can be used, but a selector or application-specific readiness signal is usually more reliable than guessing a delay.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. If a remote screenshot is all you need, one GET request returns an image or PDF. See the API documentation.

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

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
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}`);
await Bun.write('shot.webp', res);

Cookie banners, newsletter popups, and chat widgets are removed before capture; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the page verdict and billing status in headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.

Sign up free for 1,000 screenshots a month, with no card required.

Troubleshooting

Symptom Likely cause Fix
Screenshot uses the wrong responsive layout The viewport was set after navigation, or its dimensions do not match the intended CSS layout. Call setViewport() before goto() and choose the target width and height in CSS pixels.
Image is larger or smaller than expected CSS viewport dimensions and device scale factor were treated as the same setting. Set width and height for layout, then adjust deviceScaleFactor for pixel density. Check whether fullPage or clip changes the captured area.
Image captures a loading state or missing content Navigation completed before the needed dynamic content was ready. Wait for the relevant selector or application readiness signal after goto(); choose a navigation wait condition appropriate to the site.
Full-page output is unexpectedly tall fullPage: true captures the full document rather than only the viewport. Remove fullPage for a viewport capture, or use clip for a bounded area.
JPEG or WebP quality setting has no effect Quality is not applicable to PNG, or the selected format/browser does not support the expected encoding options. Use JPEG or WebP when lossy quality control is needed, and consult the screenshot options for the installed Puppeteer version.
Device screenshot does not behave like the target device A custom viewport changes dimensions but does not also set the device user agent. Use page.emulate(device) before navigation when you need a named profile’s user agent and viewport metrics together.

Performance, reliability, and cost

  • Image size and work: larger viewport dimensions, higher scale factors, and full-page capture can increase image dimensions and the work required to render and encode the screenshot. Use the smallest scope and density that meet your needs.
  • Readiness: fixed viewport settings make layout inputs explicit, but they do not make dynamic pages deterministic. Wait for content that matters, and account for animations, time-dependent content, and external resources when repeatability matters.
  • Browser lifecycle: reuse a browser process for multiple captures when building a capture service, while isolating page state per job. Always close pages and browsers when they are no longer needed.
  • Cost: Puppeteer is an open-source browser automation library, but running it still consumes your own compute, memory, storage, and engineering time. The cost depends on your infrastructure and workload; this guide makes no benchmark or price estimate.

FAQ

Does deviceScaleFactor change the page’s CSS viewport?

No. Set width and height to control CSS viewport dimensions; set device scale factor separately.

Does fullPage: true change the viewport?

It changes screenshot scope to include the full page. The viewport setting and screenshot scope are separate concerns.

Do I need a named device profile for a fixed screenshot?

No. A custom setViewport() is appropriate when you know the exact metrics you want. Emulation is useful when the named device’s user agent and viewport metrics should be applied together.

Can I get screenshot data without saving a file?

Yes. The screenshot API can return image data; omit path and choose the appropriate encoding for your application.