Puppeteer Full-Page Screenshot Has the Wrong Height: How to Fix It
Fix clipped, blank, or incorrectly sized Puppeteer full-page screenshots by checking readiness, scroll containers, viewport scale, and browser versions.
If a Puppeteer full-page screenshot has the wrong height, first make sure fullPage: true reaches page.screenshot(), then compare the saved image dimensions with the rendered document height, viewport, and device scale. A short or blank result often means content was not ready, lazy-loaded content was never triggered, the page uses an inner scrolling panel, or the capture reached an environment-specific limit. A different-looking height can also come from viewport-dependent layout or device scale.
Puppeteer’s fullPage option requests a screenshot of the full page; it does not guarantee that dynamic content has rendered, that nested scrolling content becomes part of the document, or that every possible raster height is supported. The fix depends on the symptom. [Puppeteer ScreenshotOptions]
1. Confirm the screenshot call and measure the page
Start with the smallest known-good capture. Check that you are not passing a clip rectangle and that a helper or wrapper is not replacing the screenshot options.
const screenshot = await page.screenshot({
path: 'page.png',
fullPage: true,
});
const measurements = await page.evaluate(() => ({
viewportWidth: window.innerWidth,
viewportHeight: window.innerHeight,
documentWidth: document.documentElement.scrollWidth,
documentHeight: document.documentElement.scrollHeight,
bodyHeight: document.body?.scrollHeight ?? null,
}));
console.log({ measurements, screenshotBytes: screenshot.length });
Run the measurement after the page reaches the state you want to capture. Compare the document’s CSS-pixel height with the saved image’s pixel height. With a device scale factor above 1, output pixels can be larger than CSS pixels. The comparison is diagnostic, not an exact formula for every page: transforms, fixed elements, nested scroll regions, and responsive layout can affect what appears.
The API documents fullPage as false by default. It separately documents captureBeyondViewport; that option controls capture beyond the viewport and is not a substitute for requesting a full-page screenshot. Do not toggle it blindly. [Puppeteer ScreenshotOptions]
2. Wait for the content you need
A successful navigation event does not necessarily mean a single-page application has finished rendering. load and network-idle waits can help, but neither proves that every relevant component is ready. Prefer a page-specific selector or readiness condition.
await page.goto('https://example.com', { waitUntil: 'load' });
// Replace this with a selector that appears when the target content is ready.
await page.waitForSelector('[data-page-ready="true"]', { timeout: 15000 });
await page.screenshot({ path: 'page.png', fullPage: true });
If the page has no readiness marker, wait for a stable, meaningful element that appears after the content you need. A fixed delay can be a last resort for a known animation or delayed update, but it is less reliable than checking the page itself.
3. Trigger lazy-loaded content before capture
Images and sections may load only when they approach the viewport. Scroll through the document, allow loading to settle, return to the top, then measure and capture. Adapt the pause to the site rather than assuming one delay fits every page.
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
await page.waitForSelector('main');
await page.evaluate(async () => {
const step = Math.max(300, Math.floor(window.innerHeight * 0.8));
for (let y = 0; y < document.documentElement.scrollHeight; y += step) {
window.scrollTo(0, y);
await new Promise(resolve => setTimeout(resolve, 150));
}
window.scrollTo(0, 0);
});
// Wait for a page-specific image/content condition if the site exposes one.
await page.waitForTimeout(500);
await page.screenshot({ path: 'page.png', fullPage: true });
For stronger checks, inspect the images that matter and wait until they have completed loading. A page may also insert additional content as you scroll; if so, repeat the measurement after scrolling and confirm the document height has stopped changing.
4. Check for an inner scroll container
fullPage captures the document’s full page area. It does not automatically expand a fixed-height panel whose own overflow scrolls independently. Inspect the page and identify the element whose scrollTop changes when you scroll the content.
const candidates = await page.evaluate(() =>
[...document.querySelectorAll('body *')]
.filter(el => {
const style = getComputedStyle(el);
return /(auto|scroll)/.test(style.overflowY) &&
el.scrollHeight > el.clientHeight;
})
.slice(0, 20)
.map(el => ({
tag: el.tagName,
id: el.id,
className: String(el.className),
clientHeight: el.clientHeight,
scrollHeight: el.scrollHeight,
}))
);
console.log(candidates);
If the target is inside such a panel, choose a strategy based on the intended result: scroll the panel and capture successive sections, temporarily change its height and overflow in a controlled test, or capture the panel element in segments. Changing page CSS can alter layout, so compare the result with the unmodified page before relying on it.
5. Keep viewport and browser versions consistent
Viewport units such as 100vh, sticky or fixed elements, resize observers, and late-loading fonts can make sections move or change size during a full-page capture. Set a stable viewport before navigation and avoid changing it between measurement and screenshot.
await page.setViewport({
width: 1365,
height: 900,
deviceScaleFactor: 1,
});
await page.goto('https://example.com', { waitUntil: 'load' });
await page.waitForSelector('main');
await page.screenshot({ path: 'page.png', fullPage: true });
When output dimensions change after an upgrade, record the Puppeteer package version, launched browser version, executable path, viewport, and deviceScaleFactor. If you launch a separately installed Chromium through executablePath, check that it is supported by your installed Puppeteer version. A reported 2023 version comparison involved an unsupported browser pairing, so it does not establish a general device-scale bug. [Puppeteer issue #11514]
console.log('Puppeteer package:', require('puppeteer/package.json').version);
console.log('Browser:', await browser.version());
console.log('Executable:', browser.process()?.spawnfile);
6. Diagnose a repeated maximum height
If screenshots stop at the same pixel height across runs, compare that repeated value with the requested page height and try a shorter page in the same browser environment. A Puppeteer issue from 2017 reported a 16,384-pixel ceiling in that reporter’s setup. It is historical evidence, not a current universal Chrome or Chromium limit. [Puppeteer issue #359]
If you confirm an environment-specific ceiling, capture overlapping vertical segments and stitch them, or generate a PDF if the actual requirement is a full-page document rather than one raster image. Segments should overlap enough to avoid gaps; account for sticky elements and content that changes while scrolling.
Fixes by symptom
| What you see | Likely cause | What to do |
|---|---|---|
| Only the viewport is saved | fullPage was omitted, overridden, or an unintended clip is active. |
Log the final options object and verify the direct page.screenshot({ fullPage: true }) call. |
| Correct document measurement, but bottom content is blank or missing | Content is still rendering, lazy loading has not run, or the content sits in a nested scroll area. | Wait for a page-specific readiness signal, scroll to trigger loading, and inspect overflow containers. |
| The capture ends at the same height each time | Possible browser or environment capture boundary. | Reproduce with a shorter page; if confirmed, use overlapping segments or PDF output. |
| Sections shift or appear differently sized | Viewport-relative CSS, sticky/fixed elements, late layout changes, or different viewport settings. | Hold viewport and scale constant; wait for fonts and content; inspect layout rules. |
| Dimensions changed after browser update | Different browser executable/version pairing or scale settings. | Record Puppeteer and browser versions, executable path, viewport, and device scale; use a supported pairing. |
Historical reports describe full-page layout shifts and site-specific blank or top-only captures, but they do not establish one universal cause. Reproduce against your current Puppeteer and Chromium versions before treating a report as a general bug. [issue #5411, issue #5318]
Performance, reliability, and cost considerations
- Performance: Full-page images consume memory in proportion to their pixel area. A very tall page at a high device scale factor can produce a large image and take longer to encode. Capture only the required area, or use segments when one giant raster is not necessary.
- Reliability: Make readiness checks explicit and use a consistent browser/version pairing. For pages with lazy loading, measure after the scroll pass. Keep diagnostic logs with the dimensions and versions so repeated failures can be compared.
- Cost: Self-hosted Puppeteer has no per-screenshot API charge, but your browser runtime, compute, storage, and maintenance have costs. For a managed service, check its own billing and failure policies before moving a workload.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server from ScreenshotNeo. A single GET request can return an image or PDF; see the API documentation for options. For this Puppeteer height issue, the API avoids managing a local browser capture pipeline; it cannot make every page’s own layout or content behavior disappear.
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 require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
Cookie banners are accepted and removed before the shot, along with known newsletter popups and chat widgets. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers report the page verdict and billing status. An MCP server lets AI agents use screenshot tools. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.
Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.
FAQ
Does fullPage: true include content inside an iframe?
It captures the page area, but cross-origin and independently scrolling frame content may need separate inspection or capture. Check the frame and its scroll behavior directly.
Should I set captureBeyondViewport to fix this?
Not as a first step. It is a separate screenshot option with its own semantics. Confirm that fullPage reaches the call and diagnose readiness, scrolling, scale, and browser pairing first.
Is 16,384 pixels Puppeteer’s current maximum?
The cited value comes from an individual 2017 report. The available evidence does not establish it as a universal current limit.


