ScreenshotNeo

BlogHow-to

How to Fix Cut-Off Content in Puppeteer Full-Page Screenshots

If a Puppeteer full-page screenshot is cut off, check capture scope, content readiness, page height, and browser compatibility in that order.

By the ScreenshotNeo team4 October 20269 min read

If a Puppeteer screenshot stops at the viewport or cuts off lower content, start with await page.screenshot({ path: 'page.png', fullPage: true }). Then check for a restrictive clip, make sure the content has rendered before capture, compare the output image dimensions with the document height, and verify the Puppeteer and Chromium versions. In Puppeteer 25.12.0, fullPage defaults to false, so set it explicitly. Puppeteer ScreenshotOptions.

This guide walks through those checks in order, with runnable JavaScript, cURL, Python, and Node.js examples, plus remedies for unusually long pages and element screenshots.

1. Confirm that the screenshot captures the full page

Use Page.screenshot() with fullPage: true when you want the whole document rather than the visible viewport:

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

A minimal complete example, using a page-specific readiness selector, looks like this:

import puppeteer from 'puppeteer';

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

try {
  const page = await browser.newPage();
  await page.goto(url, { waitUntil: 'networkidle2' });
  // Replace this with a selector that means the content you need is ready.
  await page.waitForSelector('#content-ready');
  await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
  await browser.close();
}

Replace https://example.com and #content-ready with values for your page. If there is no reliable selector, remove that wait and use an application-specific readiness condition, as described below. Puppeteer’s guide shows networkidle2 as a navigation wait option, but navigation completion alone does not prove that every lazy or dynamic section is ready. Puppeteer screenshot guide.

2. Check for clipping and element-level capture

The clip option deliberately limits the capture to a rectangle. If you expect the full page, remove the clip or confirm that its coordinates, width, and height cover the area you intend. A clip can make a correctly functioning screenshot look cut off.

// Full page: no clip rectangle
await page.screenshot({ path: 'page.png', fullPage: true });

// A deliberate rectangular capture, which can exclude content outside it
await page.screenshot({
  path: 'section.png',
  clip: { x: 0, y: 0, width: 1200, height: 800 }
});

captureBeyondViewport controls whether capture can extend outside the viewport for applicable capture settings. It does not replace the normal whole-document request: use fullPage: true when the target is the full page. Check the current ScreenshotOptions API for the exact option behavior in your installed Puppeteer version.

If you are capturing a single element, distinguish ElementHandle.screenshot() from Page.screenshot(). The former targets the element, not the entire document. Inspect the element’s rendered bounds and scroll position, and use a page screenshot with fullPage: true if your goal is the whole document. A historical issue reported clipping for an element larger than the viewport; that report is not proof that every current version behaves the same way. Puppeteer issue #2423.

3. Wait for the actual content, not just navigation

A page can finish its initial navigation while JavaScript is still adding content. Lazy-loaded images may appear only after scrolling into view, and an application may render a section after a data request or client-side event. Wait for the condition that matters to your page before taking the screenshot.

await page.goto(url, { waitUntil: 'networkidle2' });
await page.waitForSelector('#content-ready');
await page.screenshot({ path: 'page.png', fullPage: true });

Use a selector that only appears when the content is ready. If readiness depends on a known application event, wait for that condition instead. For lazy content, scroll through the document before capture and allow the page’s own loading behavior to complete; a fixed delay can help only when the site’s behavior is predictable, and it is less reliable than waiting for a meaningful state.

await page.goto(url, { waitUntil: 'networkidle2' });
await page.evaluate(async () => {
  const step = Math.max(window.innerHeight, 400);
  for (let y = 0; y < document.body.scrollHeight; y += step) {
    window.scrollTo(0, y);
    await new Promise(resolve => setTimeout(resolve, 100));
  }
  window.scrollTo(0, 0);
});
await page.waitForSelector('#content-ready');
await page.screenshot({ path: 'page.png', fullPage: true });

The short delay in this example is only a pacing mechanism while scrolling; it is not a universal guarantee that every site’s lazy content has loaded. For pages with known image or content selectors, wait for those items or for an application readiness signal. An older issue reported partial captures on dynamic publisher pages despite fullPage: true, illustrating why render readiness must be checked separately from capture scope. Puppeteer issue #1907.

4. Compare document height with the image dimensions

If the capture ends at a consistent boundary, inspect both the document and the generated image. This separates a page that never rendered the lower content from an output that was cut during capture or constrained by image limits.

const pageMetrics = await page.evaluate(() => ({
  bodyHeight: document.body.scrollHeight,
  documentHeight: document.documentElement.scrollHeight,
  viewportHeight: window.innerHeight
}));
console.log(pageMetrics);

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

Then inspect the resulting PNG’s pixel dimensions with an image metadata tool or library. If its height is much lower than the document height, revisit readiness, clipping, and runtime compatibility. If the page is exceptionally long, a single image may run into practical browser, memory, or image-processing limits.

A 2017 GitHub issue reported a 16,384-pixel image-height boundary in that reporter’s environment. Treat it as a historical user report, not as a universal limit for current Puppeteer or Chromium. Puppeteer issue #1135.

For very long documents, capture overlapping vertical sections and stitch them if one full-height image is not viable. Keep the overlap large enough to align fixed headers and avoid gaps; account for sticky elements that may repeat in each segment. Another option is to capture and process logical sections separately when a single composite image is not required.

5. Record and check the Puppeteer and Chromium versions

Puppeteer is designed to work with a particular browser build. If using a separately installed Chromium, record both versions and check whether that pairing is supported by your Puppeteer release. The browser version is available from the launched browser object:

console.log('Puppeteer package:', process.env.npm_package_dependencies_puppeteer);
console.log('Browser version:', await browser.version());

For a more dependable package version report, run npm ls puppeteer in the project and record its output alongside await browser.version(). Prefer Puppeteer’s bundled browser unless you have a reason to use a separately managed Chromium. A 2023 issue discussion attributed a reported mismatch to older Puppeteer versions paired with Chromium 119 at that time; it is a historical example, not a current compatibility table. Puppeteer issue #11191.

6. A diagnostic sequence that isolates the cause

  1. Reproduce with the smallest script you can, explicitly setting fullPage: true.
  2. Remove any clip and confirm you are using Page.screenshot() for a whole-page capture.
  3. Immediately before capture, check that the expected lower-page content exists in the DOM.
  4. Wait for the application’s real readiness condition; do not assume a navigation event means lazy content is finished.
  5. Compare the document’s scroll height with the image’s actual pixel height.
  6. If the page is very long, try overlapping segments and inspect for sticky elements or lazy content.
  7. Record the Puppeteer package version, Chromium version, viewport, and screenshot options. Check their supported pairing.
  8. If the problem remains, prepare a minimal HTML reproduction with those details for a bug report.

The option meanings above come from Puppeteer’s API and guide; this sequence is a practical way to distinguish scope, readiness, geometry, and environment causes.

7. Common errors and fixes

Symptom Likely cause Fix
Screenshot ends at viewport height fullPage is omitted or false Set fullPage: true explicitly.
Screenshot ends at a custom rectangle A clip rectangle constrains the capture Remove the clip for a whole-page image, or enlarge it intentionally.
Bottom section is absent from both screenshot and DOM Dynamic content has not rendered yet Wait for the page’s real selector, event, or data condition before capture.
Images or cards are missing lower down Lazy loading has not been triggered Scroll through the page and wait for the relevant content to load.
Only the selected element is captured An element handle screenshot is being used Use Page.screenshot({ fullPage: true }) for the whole document; inspect element bounds for element captures.
Output stops at a stable height on a very long page Browser or image pipeline limits, memory pressure, or page geometry Check output dimensions and capture overlapping sections if needed.
Results differ across machines or browser installs Puppeteer and Chromium pairing differs Record both versions and use a supported pairing, preferably Puppeteer’s bundled browser.
Lower page is blank despite full-page dimensions Content is not painted, an overlay obscures it, or the page uses unusual layout behavior Inspect the DOM and computed layout near the boundary; wait for paint/readiness and test a minimal reproduction.

8. Performance, reliability, and cost considerations

Full-page captures can use substantial memory because the browser must render and encode a tall image. Very long pages take more time and may produce large files. Use a bounded viewport or element capture when that is all you need; for a complete long page, consider section captures, and avoid loading resources the screenshot does not require when doing so will not change the output.

Readiness checks improve reliability. A fixed sleep can waste time on fast pages and still be too short on slow ones; a meaningful selector or application state is usually a better condition. Browser version consistency also reduces environment-specific differences. Puppeteer itself has no per-screenshot charge described by the cited API sources; infrastructure and browser runtime costs depend on where and how you run it, so size concurrency and memory to your workload.

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. It can take the capture after removing cookie banners, newsletter popups, and chat widgets; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents use screenshot tools, and it offers 1,000 screenshots a month free with no card; paid plans start at $5 for 3,000.

For a full-page capture, pass the full_page option. See the ScreenshotNeo API documentation for the available parameters and response details.

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

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com", "full_page": "true"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://stripe.com',
  full_page: 'true'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));

The service has caching with a configurable TTL, async jobs with signed webhooks, bulk capture for up to 100 URLs per call, and a usage API. The request is one API call instead of a browser setup; inspect the response headers to see the page verdict and billing status. Read the docs, then sign up for 1,000 free screenshots a month with no card.

FAQ

Does fullPage: true wait for content to finish loading?

No. It requests a full-page capture, but your script must still wait for the content your application renders asynchronously.

Should I set captureBeyondViewport: true instead?

Not as a general substitute for fullPage: true. Check the option’s version-specific API behavior and use the setting that matches your intended capture scope.

Why does my full-page image include repeated sticky headers?

Some pages keep headers fixed while the page is scrolled or laid out for capture. Inspect the page’s CSS and consider capturing sections with a deliberate handling strategy for fixed elements.

What should I include in a Puppeteer bug report?

A minimal reproduction, screenshot options, viewport configuration, Puppeteer package version, browser version, document height, and resulting image dimensions.