How to capture a screenshot of a long page without cutting off fixed headers in Puppeteer
Use Puppeteer’s full-page, clip, and element screenshot options, then handle fixed headers with a page-specific CSS adjustment when needed.
Start with page.screenshot({ fullPage: true }) when you want the whole rendered document. If a fixed header covers content you need to read, capture a specific rectangle with clip or temporarily hide the header with page-side CSS before capturing. Puppeteer’s documentation describes these screenshot controls but does not guarantee how every fixed or sticky header will appear in every full-page capture, so check the result with your page and installed Puppeteer version.
1. Choose the screenshot scope
Pick the API that matches the image you need:
| Need | API | What it captures |
|---|---|---|
| The full long document | page.screenshot({ fullPage: true }) |
The full page rather than just the viewport. |
| A specific rectangular region | page.screenshot({ clip: { x, y, width, height } }) |
The coordinates and dimensions you specify. |
| One page element | element.screenshot() |
The selected element; Puppeteer scrolls it into view if needed. |
Use a full-page shot first if preserving the page’s normal appearance matters. Use a clip if the desired output is a defined area, such as the content below a header band. Use an element screenshot when a particular article or panel is the intended output rather than the whole document.
2. Install Puppeteer and launch a browser
In a new Node.js project, install Puppeteer:
npm install puppeteer
Save the following as screenshot.mjs. Set TARGET_URL to the page you can access and adjust the readiness condition for that site.
import puppeteer from 'puppeteer';
const targetUrl = process.env.TARGET_URL ?? 'https://example.com';
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage({
viewport: { width: 1280, height: 900 },
deviceScaleFactor: 1,
});
await page.goto(targetUrl, { waitUntil: 'networkidle2', timeout: 60000 });
await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
await browser.close();
}
Run it with:
TARGET_URL=https://example.com node screenshot.mjs
fullPage requests a capture of the whole page. Puppeteer’s API documents that option, but it does not establish one universal composition rule for fixed and sticky elements. Inspect the output before relying on it for archival, visual comparison, or automated processing.
3. Handle a fixed header that obscures content
A fixed header can cover content in the captured image. Decide whether you want to preserve the page as rendered or show the content underneath the header. If preserving the original appearance is the goal, keep the header and verify the full-page result. If the header should not cover the captured content, use a page-specific adjustment.
Option A: Clip to a region below the header
A clip controls the captured rectangle. For example, the following starts at y: 80 and captures a 1280-by-900 region. Replace these values with measurements for the target page:
await page.screenshot({
path: 'content.png',
clip: { x: 0, y: 80, width: 1280, height: 900 },
});
When a clip is supplied, captureBeyondViewport defaults to true. The clip changes what is captured; it does not alter the page layout or move content out from under a fixed header.
Option B: Temporarily hide the header
If you need content that sits underneath a fixed header, inject CSS before the screenshot. The selector is site-specific. This is a practical workaround based on Puppeteer’s page-side styling and screenshot controls, not a documented fixed-header guarantee.
const headerSelector = 'header';
const style = await page.addStyleTag({
content: `${headerSelector} { visibility: hidden !important; }`,
});
try {
await page.screenshot({ path: 'page-without-header.png', fullPage: true });
} finally {
await style.evaluate(element => element.remove());
}
visibility: hidden hides the header while retaining its layout space. If the header occupies normal flow and that empty space is undesirable, test a different treatment such as display: none; that can change layout and shift page content. Fixed and sticky positioning, nested headers, and site-specific CSS can also affect the result. Use a selector that targets only the obstruction you intend to hide.
Option C: Capture a specific element
When the goal is one article or content panel, capture that element directly:
const article = await page.waitForSelector('.article-content', { timeout: 15000 });
if (!article) throw new Error('Article content was not found');
await article.screenshot({ path: 'article.png' });
ElementHandle.screenshot() scrolls the element into view if needed and delegates to page screenshot capture. It is an element-level capture, not a request to screenshot the whole document.
4. Tune page readiness and output
Long pages often render content after the initial navigation. Choose a readiness signal that matches the page instead of assuming that navigation alone means every image and component is ready.
- Navigation:
page.goto()supports lifecycle conditions such asload,domcontentloaded,networkidle0, andnetworkidle2. A quiet-network condition can be unsuitable for pages with continuous requests. - Specific content: use
page.waitForSelector('.article-content')for a required element. - Lazy-loaded images: full-page capture does not itself promise that every lazy image has been fetched. Scroll through the page or use a site-specific loading strategy, then wait for image completion before capture.
- Viewport and scale: set the viewport before navigation if responsive layout matters.
deviceScaleFactorchanges pixel density and output size. - Output format: use a file extension and screenshot options that match your intended format, such as PNG or JPEG. Check the installed Puppeteer API reference for options supported by that version.
For repeatable results, use the same viewport, scale, readiness condition, and browser/Puppeteer version. Page content can change between captures even when the script does not.
5. Troubleshoot common problems
| Symptom | Likely cause | Fix |
|---|---|---|
| The image contains only the viewport | The screenshot call omitted fullPage: true, or used a clip/element capture. |
Use page.screenshot({ path: 'page.png', fullPage: true }) for the whole document. |
| Content is hidden behind the header | The header remains fixed or sticky in the rendered page. | Try a clip that excludes the header band or temporarily hide the page-specific header selector. Inspect whether hiding it changes layout. |
| The screenshot is missing late content | The page or its lazy-loaded assets were not ready. | Wait for a relevant selector or asset condition; for lazy content, scroll through the document before capture. |
| Navigation times out on a page that appears loaded | The page may keep making network requests, so a network-idle condition is never reached. | Use a more suitable lifecycle condition and explicitly wait for the content your capture needs. |
| The clip is blank, misplaced, or the wrong size | Clip coordinates or dimensions do not match the page and viewport geometry. | Measure the target region and adjust x, y, width, and height. Confirm the viewport was set as intended. |
| The element screenshot fails to find its target | The selector is wrong, or the element has not appeared by the timeout. | Use a selector from the rendered page and wait for it with an appropriate timeout. |
| Results changed after a Puppeteer upgrade | Screenshot behavior has had version-specific fixes and changes, including viewport reset and clip handling. | Check the changelog and API reference for the installed version, then validate the output against a known page. |
6. Reliability, performance, and cost
A full-page image can be much taller and larger in memory than a viewport capture. Large documents and high device scale factors increase image dimensions and processing work. Capture only the region or element you need when that meets the requirement. For a long page, avoid repeatedly capturing while the browser is still loading assets, and close the browser in a finally block so failures do not leave it running.
Screenshot output is sensitive to page content, font and image loading, viewport size, browser version, and CSS state. If you inject styles, remove them after capture or use a fresh page for each job. Puppeteer itself does not charge per screenshot; your costs depend on where and how you run the browser, including compute and storage. Check the API reference and changelog for your installed version because screenshot behavior can evolve.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request can return an image or PDF; the Puppeteer setup above is useful when you need direct control over browser behavior and site-specific CSS.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo API documentation for request options. Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits cost nothing, and the response identifies the page verdict and billing status in headers. An MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month, with no card required.
FAQ
Does fullPage: true guarantee a fixed header will not cover content?
No. It requests a full-page capture, but the reviewed Puppeteer documentation does not guarantee fixed or sticky element composition across pages and versions. Check the output with your target page.
Does clip move content below the header?
No. It selects the screenshot region. It does not change page layout or reposition content beneath a fixed header.
Should I use visibility: hidden or display: none?
It depends on the layout and the desired image. Visibility retains layout space; display removal can change layout. Test the selector and resulting page geometry.
What if I need a screenshot of only the article?
Wait for the article element and use its screenshot() method. Puppeteer scrolls it into view if needed.


