October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PCOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
browser automation

How to Prevent Puppeteer page.screenshot() From Resizing the Viewport

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Set the viewport explicitly before navigation, then request a viewport-only capture. Use fullPage: false, captureBeyondViewport: false, and an explicit deviceScaleFactor. This keeps the CSS viewport dimensions you chose instead of allowing an oversized clip or full-page operation to change how Chromium lays out the page.

When you need content larger than the viewport, do not silently accept a resize. Choose between a temporary resize-and-restore workflow, a controlled clip, or a stitched capture based on whether responsive CSS, sticky elements, and lazy loading must remain exactly as they appeared in the original viewport.

Use a fixed viewport for an ordinary screenshot

The most stable pattern is to set all viewport values before loading the page and to make the screenshot options explicit:

import puppeteer from 'puppeteer';

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

await page.setViewport({
  width: 1366,
  height: 768,
  deviceScaleFactor: 1,
});

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

await page.screenshot({
  path: 'viewport.png',
  fullPage: false,
  captureBeyondViewport: false,
});

await browser.close();

width and height are CSS-pixel dimensions. deviceScaleFactor controls how many device pixels are used for each CSS pixel; it changes output density and file dimensions, not the CSS layout width and height. Keep all three values explicit while diagnosing a screenshot that appears to resize.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why each option matters

  • fullPage: false captures the current viewport. It is the default, but specifying it documents the intended behavior and prevents a later refactor from changing the result.
  • captureBeyondViewport: false tells Puppeteer not to capture pixels outside the current viewport for this operation. It is particularly useful when an element clip or another oversized capture appears to trigger a resize.
  • deviceScaleFactor: 1 makes output dimensions predictable. Use 2 for a retina-style image only when the larger pixel dimensions are intentional.

Why page.screenshot() can appear to resize the page

A screenshot is not always a simple copy of the visible window. A full-page capture, an element clip, or a clip that extends beyond the viewport can require Puppeteer and Chromium to handle content outside the current viewport. During that work you may see a visible blink, a different responsive breakpoint, or an image whose dimensions do not match the viewport you configured.

Several historical issue reports describe version-specific behavior. In Puppeteer 8.0.0, one report found that setting captureBeyondViewport: false solved a resize-related screenshot problem. Another report documents a Chromium behavior change in Puppeteer 2.0: element screenshots began clipping to the viewport. Code that relied on the older behavior had to enlarge the viewport before calling page.screenshot(). These reports are not guarantees for every current Puppeteer and Chromium pairing, so reproduce the problem with the exact versions used in your project.

Full-page capture is a different operation

fullPage: true means “capture the full document,” not “capture the current viewport at higher quality.” It may involve content below the fold, altered layout calculations, and interactions with lazy loading or fixed-position elements. If your requirement is what a user sees at 1366×768, leave fullPage false.

A clip can cross the viewport boundary

Passing a clip rectangle or asking for an element screenshot can request pixels outside the current viewport. Depending on the Puppeteer and Chromium versions, that area may be clipped, rendered through an off-viewport path, or require a viewport adjustment. Treat an element screenshot as a separate case rather than adding fullPage: true to solve it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Diagnose the dimensions before changing code

  1. Log the configured viewport. Call page.viewport() immediately after setViewport() and again immediately before the screenshot.
  2. Log the browser-side layout. Evaluate window.innerWidth, window.innerHeight, document.documentElement.clientWidth, and document.documentElement.clientHeight.
  3. Record the output image dimensions. A device scale factor of 2 can produce an image twice as wide and tall in pixels while the CSS viewport remains unchanged.
  4. Check for application resize handlers. A page may listen for resize, use vh units, switch media queries, or trigger lazy loading when the viewport changes.
  5. Run a viewport-only control capture. Use the minimal example with no clip and captureBeyondViewport: false. If that is stable, the resizing is associated with the full-page or oversized-element path.
console.log('Puppeteer viewport:', page.viewport());
console.log('Browser layout:', await page.evaluate(() => ({
  innerWidth: window.innerWidth,
  innerHeight: window.innerHeight,
  clientWidth: document.documentElement.clientWidth,
  clientHeight: document.documentElement.clientHeight,
})));

Choose a strategy for content larger than the viewport

Requirement Recommended method Main trade-off
Exactly the visible viewport fullPage: false and captureBeyondViewport: false Content outside the viewport is omitted
Entire document, with layout changes acceptable fullPage: true Responsive and lazy-loading behavior can differ from the initial view
One oversized element, with its natural layout preferred Temporarily enlarge the viewport, capture, then restore it Resizing can fire listeners and change the rendering
Oversized content, while preserving the original viewport Controlled clipping or a stitched, scroll-based capture Stitching needs overlap and care around sticky or fixed elements

Documented resize-and-restore workaround for an oversized element

When an element is wider or taller than the viewport and you need one image of the complete element, save the original viewport, measure the element, enlarge the viewport to at least those bounds, take the screenshot, and restore the original values in a finally block.

const originalViewport = page.viewport();
const element = await page.$('.invoice');
if (!element) throw new Error('Element .invoice was not found');

try {
  const bounds = await element.boundingBox();
  if (!bounds) throw new Error('Element is not visible');

  await page.setViewport({
    ...originalViewport,
    width: Math.max(originalViewport.width, Math.ceil(bounds.width)),
    height: Math.max(originalViewport.height, Math.ceil(bounds.height)),
  });

  await element.screenshot({
    path: 'invoice.png',
    captureBeyondViewport: false,
  });
} finally {
  await page.setViewport(originalViewport);
}

This is useful when the element must be captured as one contiguous bitmap and changes caused by a larger viewport are acceptable. It is not a faithful “as seen” capture if the page uses height-based media queries, vh units, sticky positioning, resize observers, or intersection-triggered loading. The enlarged viewport can select different CSS, move sticky controls, or cause additional content to load.

Preserve the viewport with clipping

If fidelity to the original viewport matters more than obtaining the entire element in one operation, keep the viewport fixed and capture only a rectangle that fits inside it:

await page.screenshot({
  path: 'panel-viewport.png',
  fullPage: false,
  captureBeyondViewport: false,
  clip: { x: 0, y: 0, width: 900, height: 700 },
});

Clipping cannot include pixels that are genuinely outside the viewport when captureBeyondViewport is false. For a complete long element without resizing, scroll and capture overlapping slices, then stitch them in an image-processing step. Hide or account for fixed headers so they are not repeated in every slice.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Make lazy loading and dynamic pages deterministic

A viewport change can activate intersection observers and lazy-loaded images. Even without a resize, a screenshot taken before those resources settle can look incomplete. Navigate with an appropriate wait condition, wait for a page-specific selector, and use a short, explicit delay only when the application has no reliable readiness signal.

await page.goto(url, { waitUntil: 'networkidle0' });
await page.waitForSelector('#app');
await page.evaluate(() => document.fonts.ready);
await page.screenshot({
  path: 'stable.png',
  fullPage: false,
  captureBeyondViewport: false,
});

networkidle0 means no active network connections for the relevant quiet period; pages with analytics, streaming, or long polling may never reach it. In those cases, wait for the application’s “ready” element instead. Avoid scrolling the page merely to force lazy loading unless your capture plan includes every scroll position.

Version and Chromium considerations

Puppeteer controls a bundled or configured Chromium revision, and screenshot behavior can change between revisions. Pin the Puppeteer version in CI, record the Chromium revision, and test the exact combination after upgrades. Historical workarounds should not be copied blindly.

One old discussion mentions --blink-settings=mainFrameClipsContent=false as a workaround for captures outside the viewport. Treat that launch flag as a legacy, version-specific experiment: verify it against your Chromium revision, compare the resulting layout with a normal run, and remove it if it introduces broader rendering differences. Prefer the supported screenshot options and a deliberate capture strategy first.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Troubleshooting common symptoms

The screenshot is smaller than the configured viewport

Check whether fullPage or an element clip is being passed by a shared helper. Confirm that page.viewport() still reports the expected values immediately before capture. Also check the output format and device scale: pixel dimensions are not CSS dimensions.

The page blinks or changes breakpoints during capture

Look for a full-page request, an oversized clip, or the resize-and-restore workaround. Use fullPage: false and captureBeyondViewport: false for a control run. If the control is stable, choose clipping or stitching instead of enlarging the viewport.

An element screenshot stops at the viewport edge

This is consistent with clipping behavior introduced in Puppeteer 2.0-era Chromium integrations. Measure the element with boundingBox(). If a complete single image is required, use the documented temporary enlargement pattern; if responsive fidelity is more important, keep the viewport and capture in slices.

Lazy images are missing

Wait for the relevant selector or image decode, and ensure the page has reached its own ready state. A full-page operation may trigger different intersection behavior than a viewport capture; do not switch modes without checking the visual result.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The problem appears only after upgrading Puppeteer

Compare the old and new Puppeteer/Chromium revisions, then run the same URL with explicit viewport, scale, and screenshot options. Historical issue reports are evidence of behavior in particular versions, not a promise that the same workaround applies today.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance, reliability, and cost choices

  • Viewport-only captures are usually the least work because Chromium renders only what the test needs. They are also easiest to make repeatable.
  • Full-page captures can require more layout, image loading, and memory. Very tall documents may produce large files or expose fixed-element duplication.
  • Resize-and-restore adds a layout transition and can trigger application code twice: once for the enlargement and again when restoring.
  • Stitching preserves the original viewport but adds image-processing time and requires overlap rules for sticky or fixed content.
  • Retina scale increases output pixels and file size without changing responsive breakpoints. Use it for visual quality, not to solve a layout problem.

Or skip the browser setup

ScreenshotNeo provides a website screenshot API when you do not need to maintain Puppeteer and Chromium yourself. One GET request returns PNG, JPEG, WebP, or PDF. Before capture it accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

For a direct call, see the ScreenshotNeo API documentation:

Best Value
The SQL Programming Language: .
  • Used Book in Good Condition
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

The same request in Python:

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)

And in Node.js:

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 failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo includes full-page and element capture, device presets and custom viewports, retina scale, dark mode, lazy-image loading, custom CSS and JavaScript, click and wait controls, hidden selectors, request and resource blocking, headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, configurable caching, signed image links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API, and an OpenAPI specification. Every feature is on every plan: 1,000 shots per month are free without a card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Does setting deviceScaleFactor prevent viewport resizing?

No. It controls device-pixel density and output dimensions. Set it explicitly for predictable images, but use fullPage and captureBeyondViewport settings to control capture boundaries.

Should I always set captureBeyondViewport to false?

Use it for a viewport-faithful screenshot or while diagnosing an unexpected resize. It is not appropriate when your intended capture deliberately includes content outside the viewport.

Can I capture a complete element without changing responsive CSS?

Not reliably as one oversized bitmap. Keep the viewport fixed and use controlled clips or overlapping scroll slices when preserving the original responsive layout is the priority.

Why does a screenshot differ between local development and CI?

Puppeteer and Chromium revisions, device scale, fonts, and page readiness can differ. Pin versions, set the viewport before navigation, and wait for the same application-ready condition in both environments.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.