The complete guide to capturing entire scrollable pages as a single image.
A standard screenshot captures only the visible viewport — typically 1280×720 or 1440×900 pixels. But many pages scroll for thousands of pixels. Full-page capture stitches the entire scrollable content into a single image, giving you the complete visual representation of a page.
Modern headless browsers handle this natively. When you set fullPage: true, the browser measures the total scroll height of the page, resizes its internal canvas to match, renders the entire content, and outputs a single image covering the full height.
Internally, it's not literally scrolling and stitching — the browser renders the full page in one pass at the computed height. This is faster and produces cleaner results than manual scroll-and-stitch approaches.
import { chromium } from 'playwright';
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
await page.goto('https://example.com', { waitUntil: 'networkidle' });
await page.screenshot({ path: 'full.png', fullPage: true });
await browser.close();
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 900 });
await page.goto('https://example.com', { waitUntil: 'networkidle0' });
await page.screenshot({ path: 'full.png', fullPage: true });
await browser.close();
curl -X POST https://api.nightglass.xyz/api/v1/screenshot \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_API_KEY" \
-d '{"url": "https://example.com", "fullPage": true}' \
--output fullpage.png
This is the most common gotcha with full-page screenshots. Modern sites lazy-load images — they only fetch images when the user scrolls them into view. In a full-page capture, images below the initial viewport never enter the viewport (the browser renders the full height in one pass), so they remain unloaded.
The fix: scroll the page before capturing to trigger lazy loading:
// Scroll the page to trigger lazy loading
await page.evaluate(async () => {
await new Promise((resolve) => {
let totalHeight = 0;
const distance = 500;
const timer = setInterval(() => {
window.scrollBy(0, distance);
totalHeight += distance;
if (totalHeight >= document.body.scrollHeight) {
clearInterval(timer);
window.scrollTo(0, 0); // Scroll back to top
resolve();
}
}, 100);
});
});
// Now all images are loaded — capture
await page.screenshot({ path: 'full.png', fullPage: true });
Some pages load more content as you scroll (social feeds, search results). Full-page capture of these pages will either capture only the initially loaded content or scroll forever. Set a maximum scroll height and accept that you'll capture a finite portion:
const MAX_HEIGHT = 10000; // Cap at 10,000px
await page.evaluate(async (maxH) => {
await new Promise((resolve) => {
let totalHeight = 0;
const timer = setInterval(() => {
window.scrollBy(0, 500);
totalHeight += 500;
if (totalHeight >= maxH || totalHeight >= document.body.scrollHeight) {
clearInterval(timer);
window.scrollTo(0, 0);
resolve();
}
}, 200);
});
}, MAX_HEIGHT);
Pages with position: fixed or position: sticky headers will render the header at every scroll position in a full-page capture, creating duplicated headers throughout the image. Some browsers handle this well, others don't.
Workaround: inject CSS to change fixed elements to relative or absolute before capturing.
await page.addStyleTag({
content: '* { position: static !important; }'
});
This is aggressive — it'll break some layouts. A more targeted approach is to hide specific fixed elements by selector.
Full-page images are large. A page that scrolls for 5000 pixels at 1440px wide at 2x DPR produces a 2880×10000 pixel image — potentially 10–30MB as PNG. Use JPEG for full-page captures unless you specifically need lossless quality. JPEG at quality 80 will be 5–10x smaller than PNG.
nightglass supports fullPage: true in the API request body, handling the scroll, lazy loading, and capture automatically. See the screenshot endpoint reference.
For step-by-step tutorials on full-page capture in each library: Playwright tutorial and Puppeteer guide. For batch processing many pages, the scale architecture guide covers queue patterns. Full-page captures are the foundation of visual change monitoring.