ScreenshotNeo

BlogHow-to

HTML to Image with Puppeteer: Set Viewport and Device Scale Factor

Set Puppeteer’s viewport and device scale factor before rendering, then choose viewport, full-page, or clipped screenshots for predictable HTML-to-image output.

By the ScreenshotNeo team4 October 20268 min read

To control the size and pixel density of a Puppeteer screenshot, call page.setViewport() with explicit width, height, and deviceScaleFactor before navigating, then capture with page.screenshot(). Set the viewport first when the page layout responds to its dimensions; choose screenshot options such as fullPage, clip, or omitBackground based on the output you need.

This guide uses Puppeteer’s documented API. The examples are illustrative; run them in your own environment and inspect the resulting image dimensions for your setup.

1. Install Puppeteer and prepare a page

In a new Node.js project, install Puppeteer:

npm install puppeteer

Save the following as screenshot.mjs. It opens a page, sets its viewport before navigation, waits for the page to load, and writes a PNG:

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({ headless: true });

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

  await page.setViewport({
    width: 640,
    height: 480,
    deviceScaleFactor: 2,
  });

  await page.goto('https://example.com', { waitUntil: 'networkidle0' });
  await page.screenshot({ path: 'page.png', type: 'png' });
} finally {
  await browser.close();
}

Run it with node screenshot.mjs. The viewport is 640 CSS pixels wide by 480 CSS pixels high, with a device scale factor of 2. Check the saved file’s actual dimensions in your environment; do not infer every output’s raster dimensions from the viewport alone, especially when using other screenshot options.

2. Set width, height, and device scale factor

page.setViewport() configures the page viewport. Use explicit values when you need repeatable layout inputs:

await page.setViewport({
  width: 1280,
  height: 800,
  deviceScaleFactor: 1,
});
Setting What it controls When to set it
width Viewport width in CSS pixels. Set it to the layout width you want the page to respond to.
height Viewport height in CSS pixels. Set it to the visible page height for a viewport capture.
deviceScaleFactor The page’s device scale factor, affecting rendering density. Use an explicit value for consistent density across runs.

Puppeteer’s documented method recommends setting the viewport before navigation. If no viewport is set on the connection, the documented default is 800 by 600. page.viewport() reports the viewport from the last setViewport() call or the connection default; it does not inspect the browser’s actual viewport.

Changing mobile or touch settings such as isMobile or hasTouch can trigger a reload. Configure these before navigation where possible, and avoid changing them mid-capture unless a reload is intended.

References: Page.setViewport(), ConnectOptions, and Page.viewport().

3. Choose custom dimensions or a device profile

Use a custom viewport when the requirement is a specific width, height, and density. Use page.emulate() when you need to represent a documented device profile: it sets both the device user agent and viewport. Puppeteer recommends emulating before navigation.

import puppeteer from 'puppeteer';
import { devices } from 'puppeteer';

const browser = await puppeteer.launch({ headless: true });

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

Use a profile when user agent and viewport should move together. Use setViewport() when you need a custom render size and do not need a device profile. For mobile-specific behavior, remember that changing mobile or touch configuration can cause a reload.

Reference: Page.emulate().

4. Pick the screenshot region and image options

Use the screenshot option that matches the intended result:

Need Option or method Notes
Visible viewport page.screenshot() with defaults Captures the page’s current visible area.
Entire document fullPage: true Use when content below the viewport must be included.
Specific rectangle clip Define the capture region explicitly.
Transparent page background omitBackground: true Useful when the page background should remain transparent in the output.
File output path Writes the image to the named path.
JPEG compression type: 'jpeg' and quality Quality applies to JPEG; it does not apply to PNG.

For example, a full-page PNG can use the same viewport setup:

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

A clipped region can be captured like this:

await page.screenshot({
  path: 'region.png',
  clip: { x: 40, y: 60, width: 500, height: 300 },
});

For a transparent background, use PNG and omit the page background:

await page.screenshot({
  path: 'transparent.png',
  type: 'png',
  omitBackground: true,
});

Puppeteer also documents element screenshots. The element screenshot method attempts to scroll an element into view if it is hidden. Consult the official guide and options reference for the API details and option types: Screenshots, Page.screenshot(), and ScreenshotOptions.

5. Render HTML you provide

For HTML-to-image work that starts with a local string rather than a URL, set the viewport before loading the markup, then use page.setContent() and capture:

import puppeteer from 'puppeteer';

const html = `<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <style>
      body { font: 20px sans-serif; margin: 24px; }
      .card { padding: 24px; border: 1px solid #ccc; border-radius: 12px; }
    </style>
  </head>
  <body>
    <div class="card">Rendered with Puppeteer</div>
  </body>
</html>`;

const browser = await puppeteer.launch({ headless: true });

try {
  const page = await browser.newPage();
  await page.setViewport({ width: 800, height: 600, deviceScaleFactor: 2 });
  await page.setContent(html, { waitUntil: 'load' });
  await page.screenshot({ path: 'html.png', type: 'png' });
} finally {
  await browser.close();
}

External fonts, images, and stylesheets referenced by your markup still need to load. If the screenshot is missing an asset, check that its URL is reachable from the browser process and that the page has finished loading before capture.

6. Understand dimensions and image output

The viewport dimensions describe the page’s CSS-pixel layout area. The device scale factor configures rendering density. These settings are distinct from the headless screen configuration: Puppeteer’s screen guide describes a default headless screen of 800 by 600 unless configured through the documented screen or window-size switches. A screen setting is not a guarantee of every screenshot’s raster dimensions.

For predictable output:

  1. Set viewport width, height, and device scale factor explicitly.
  2. Choose viewport, full-page, clip, or element capture intentionally.
  3. Choose an image type and transparency or quality settings that suit the destination.
  4. Inspect the actual image dimensions and appearance in your own Puppeteer environment.

Reference: Screen configuration.

7. Troubleshoot common problems

Symptom Likely cause Fix
Layout does not match the target width The page navigated before the viewport was configured, or the viewport values are not explicit. Call setViewport() before navigation and set both dimensions.
Image is not as dense as expected The device scale factor was omitted or the output was judged only from CSS dimensions. Set deviceScaleFactor explicitly and inspect the output file’s pixel dimensions.
Mobile layout or touch behavior is inconsistent A mobile/touch setting changed after navigation and triggered a reload, or only the viewport was changed while the user agent was not. Use page.emulate() before navigation for a device profile, or configure the intended settings before loading the page.
Only the visible part of the page appears A normal screenshot captures the viewport. Set fullPage: true for the document, or use a clip or element screenshot for a specific region.
Transparent pixels appear opaque The page background was included. Use omitBackground: true and a format that supports transparency, such as PNG.
JPEG quality has no effect The image is being saved as PNG. Use JPEG when lossy quality control is needed; the documented quality option does not apply to PNG.
Screenshot is missing images, fonts, or styles Assets have not loaded, are unreachable, or are blocked. Check asset URLs and browser access; wait for the relevant page readiness condition before capturing.
page.viewport() does not match an observed browser window The method returns the recorded viewport setting or connection default, not a live inspection of the actual browser viewport. Use the value you set as the source of truth and inspect the produced screenshot for output verification.

8. Performance, reliability, and cost

Rendering costs come from launching and running a browser, loading the page and its assets, and producing the image. Reuse a browser process for multiple captures when your application architecture permits it, while creating or resetting pages so each capture receives the intended viewport and state. Full-page captures can include more content than viewport captures; use them only when the full document is needed.

For reliability, configure the viewport before navigation, select a readiness condition appropriate to the page, and ensure the browser can reach required assets. Dynamic pages may need an explicit wait for the content that matters. A network-idle condition can be unsuitable for pages with ongoing network activity, so choose a wait strategy that fits the target rather than assuming one condition works for every site.

Puppeteer itself does not set a per-screenshot service price in this workflow. Your cost depends on the machine or browser infrastructure you operate and the time and resources each capture consumes. If you need a managed screenshot API instead of operating browser infrastructure, see the option below.

9. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request returns a PNG, JPEG, WebP, or PDF. Its capture flow accepts cookie and consent banners like a visitor and removes 60+ 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, and the response identifies the page verdict and billing status in headers. AI agents can use its MCP tools, including take_screenshot, get_page_info, and capture_pdf.

Install the Python dependency with pip install requests, then run:

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()
open("shot.webp", "wb").write(r.content)

Equivalent cURL and Node.js calls:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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 Bun.write('shot.webp', res);

Use a valid API key in place of YOUR_API_KEY. The API base is documented here. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. Every feature is on every plan.

Sign up for ScreenshotNeo and get 1,000 screenshots a month free with no card.

10. Frequently asked questions

Does page.viewport() tell me the screenshot’s pixel dimensions?

No. It reports the configured viewport or connection default. Inspect the saved image for its actual raster dimensions.

Should I use a device scale factor of 1 or 2?

Choose based on the density you need and the output size you can handle. Keep it explicit so repeated captures use the same rendering configuration.

Does device emulation only change the viewport?

No. Puppeteer’s device emulation sets both the user agent and viewport. Use a custom viewport when you need specific dimensions without a device profile.

Can I take a screenshot of one element instead of the page?

Yes. Puppeteer’s screenshot guide documents element screenshots; it attempts to scroll a hidden element into view before capturing it.

Can I use JPEG quality with PNG?

No. The documented quality option applies to JPEG and has no effect on PNG.