Skip to content
Field guideBy 11 min read

Why website screenshots look wrong, and how to fix each case

A field guide to the 11 ways a capture of a web page goes wrong. Spot the symptom, read what the browser was doing, and apply the fix, by hand or in an automated capture.

  1. 1.Blank image boxes
  2. 2.Spinners and skeletons
  3. 3.Cookie banner on top
  4. 4.Repeated sticky header
  5. 5.Cut-off page
  6. 6.Fallback fonts
  7. 7.Frozen mid-animation
  8. 8.Wrong layout for the device
  9. 9.Blurry text
  10. 10.Login wall
  11. 11.Bot-check page
  12. The page as it should look
The 11 symptoms in this guide, and the complete page they are all broken versions of. Choose one to jump to its entry.

What is wrong with my screenshot? A diagnosis table

A broken website screenshot is nearly always an accurate record of the page at the wrong moment or in the wrong state. Find what you see in the first column and follow the link to its entry.

If you seeIt isFirst thing to try
Empty or grey boxes where images should be, mostly further down the page1. Blank image boxesScroll through the page before the capture
A spinner, grey skeleton blocks, an empty chart or a "Loading" label2. Spinners and skeletonsWait for an element of the real content, not for a fixed time
A consent box over the bottom of the page, or a dimmed overlay over all of it3. Cookie banner on topBlock or hide the banner, or set the consent cookie first
The navigation bar again and again down a long screenshot, or a bar across the middle4. Repeated sticky headerHide the bar or make it static before a full-page capture
The screenshot stops at the first screen, or ends partway down5. Cut-off pageTurn full-page capture on and check the height limit
Text in a default serif or system font, different line breaks, or missing text6. Fallback fontsWait for the fonts to finish loading
Sections half transparent or shifted, or two captures of one page that differ7. Frozen mid-animationDisable animations, or ask the page for reduced motion
A phone-sized screenshot of the desktop site in tiny text, or the mobile menu on desktop8. Wrong layout for the deviceSet width, height and mobile emulation together
Soft or pixelated text and icons, most of all on a laptop or phone screen9. Blurry textCapture at a device scale factor of 2
A sign-in form in place of the page you asked for10. Login wallSend a session cookie or a token with the request
A "checking your browser" page, a CAPTCHA or an "Access denied" message11. Bot-check pageAllow your capture tool on your own site; ask the owner on anyone else's

The 11 symptoms of a broken screenshot, with the cause and fix for each

Each entry shows the symptom as a small wireframe page: grey bars are text, tinted boxes are images. The last line of an entry names the option in CurlShot that applies the fix, where one exists.

1.Blank image boxes

What you see
The top of the page is fine. Further down, images are empty boxes, grey placeholders or blurred thumbnails.
Why it happens
An image marked loading="lazy" is not fetched until it reaches a distance from the viewport that the browser calculates, and the load event does not wait for it. Scripts built on IntersectionObserver hold back images and whole sections the same way. A capture that never scrolls never triggers them.[1][7][3]
Fix it by hand
Scroll slowly to the bottom, wait for the last images, scroll back to the top, then capture.
Fix it in an automated capture
Scroll the page in steps of about one viewport before the capture, pause at each step, then check that every img reports complete.
In CurlShot
full_page=true scrolls through the page first. That step is full_page_scroll, on by default. The scroll has a time budget, so on a very long page lower full_page_max_height. Full-page screenshots

2.Spinners and skeletons

What you see
A spinner, grey skeleton blocks, an empty chart or a "Loading" label sits where the content should be.
Why it happens
The load event covers the page and the files it lists, not the data its scripts request afterwards. An app that renders in the browser fetches that data after load, so a capture at load shows its loading state. Network idle, which Playwright and Puppeteer define as no network connections for at least 500 ms, comes later, and Playwright marks it as discouraged for tests in favour of checking for the content itself.[7][8][9]
Fix it by hand
Wait until the content is on screen, then capture.
Fix it in an automated capture
Wait for a selector that exists only once the real content is drawn, such as a table row or the chart itself. Use network idle when you cannot name one, and a fixed delay as the last resort.
In CurlShot
wait_for_selector waits until the element is visible. wait_until=networkidle is the fallback, and delay adds up to 30 seconds after both. Waiting and timing

5.Cut-off page

What you see
The screenshot stops at the first screen, or ends partway down with the footer missing.
Why it happens
By default a capture is the viewport, not the page: fullPage is off in both Playwright and Puppeteer until you set it. Tools also cap the height of a full-page image, so an endless feed cannot produce an endless file. A third cause: the content scrolls inside an inner container, so the document itself is only one screen tall.[8][10]
Fix it by hand
Choose the full-page option, not the visible area. Capture a very long page in sections.
Fix it in an automated capture
Turn full-page capture on and know your tool's height limit. For an inner scroll container, give the viewport enough height that the container no longer needs to scroll.
In CurlShot
full_page=true. The cut-off height is full_page_max_height: 20,000 pixels by default, 30,000 at most, and a plan can set a lower ceiling. A taller page is cut off there; the request does not fail. Very tall pages
Our pricingSimple plans for every team

6.Fallback fonts

What you see
Headings and text are in a default serif or system font, lines wrap differently from the live site, or text is missing altogether.
Why it happens
A web font is a separate download. During the font block period the browser draws the text invisibly, then with a fallback font until the real one arrives; how long each phase lasts depends on the page's font-display value. A capture in that window records the fallback. document.fonts.ready resolves only once font loading and the layout that follows are finished.[5][6]
Fix it by hand
Wait until the text settles, or reload once so the fonts come from the cache.
Fix it in an automated capture
Await document.fonts.ready in the page before capturing. If the font never arrives, look for a failed font request in the network log.
In CurlShot
There is no font-specific option. wait_until=networkidle or a short delay covers a late font. Check that the request does not send block_resources=font, which draws the page in fallback fonts on purpose. Waiting and timing

7.Frozen mid-animation

What you see
Sections are half transparent, shifted by a few pixels or missing, and two captures of the same page do not match.
Why it happens
A screenshot records a single frame. Entrance animations start on load or when a section scrolls into view, so a capture taken during one freezes it partway. Playwright's animations: "disabled" shows the clean fix: finite animations are fast-forwarded to their end and infinite ones are reset to their start. A site that honours prefers-reduced-motion skips the motion when the browser reports that preference.[8][11][3]
Fix it by hand
Wait for the motion to finish. The reduce motion setting of your system stops it on sites that respect the setting.
Fix it in an automated capture
Disable CSS animations and transitions at capture time, emulate reduced motion, and scroll through the page first so scroll-triggered reveals have already played.
In CurlShot
reduced_motion=true reports the preference to the page and, for image captures, stops CSS animations and transitions at the moment of capture. Add a short delay for motion driven by JavaScript.

8.Wrong layout for the device

What you see
A phone-sized screenshot shows the desktop site shrunk to tiny text, or a desktop screenshot shows the mobile menu.
Why it happens
Layout follows the viewport width, and a narrow window alone is not a phone. Mobile browsers read the viewport meta tag; without mobile emulation, or on a page that lacks width=device-width, the page can be laid out in a virtual viewport of around 980 pixels and shrunk to fit. Some sites also pick a layout from the user agent.[12]
Fix it by hand
Use the browser's device mode and pick a device, rather than dragging the window narrower.
Fix it in an automated capture
Set width, height, mobile emulation, touch and user agent together. A device preset does all of it in one setting.
In CurlShot
viewport_device=iphone_15_pro sets size, density, mobile mode and user agent at once. By hand: viewport_width, viewport_height and viewport_mobile=true. With none of them the window is 1280 by 1024, desktop. Device presets
Sharp textSharp text1x2x

9.Blurry text

What you see
Text and icons look soft or pixelated next to the live page, most of all on a laptop or phone screen.
Why it happens
devicePixelRatio is the ratio of physical pixels to CSS pixels: 1 on a classic 96 DPI display, 2 on HiDPI and Retina screens. A capture at scale 1 holds one image pixel per CSS pixel, so a 2x screen has to stretch each one over four physical pixels. Image width = viewport width x scale, so 1280 x 2 = 2560 pixels.[13][14]
Fix it by hand
Capture on a high-density screen, or add a device pixel ratio of 2 in DevTools device mode before capturing.
Fix it in an automated capture
Set the device scale factor to 2, or 3 for a phone. Enlarging a scale 1 image afterwards does not bring the detail back.
In CurlShot
device_scale_factor takes 1 to 3 and the default is 1. Scale 2 makes 2 x 2 = 4 times the pixels, so use JPEG or WebP for tall pages. A plan can cap the scale of full-page captures. High-resolution screenshots

10.Login wall

What you see
The screenshot shows a sign-in form or a "please log in" page, not the dashboard you asked for.
Why it happens
A session lives in a cookie or a token held by your own browser. An automated browser starts empty, so the site redirects it to the sign-in page, and the capture is a faithful screenshot of that.
Fix it by hand
Sign in first in the same browser, then capture.
Fix it in an automated capture
Give the browser a session before it loads the page: a session cookie, a token header or HTTP credentials. Use an account made for this job, with read-only rights.
In CurlShot
cookies for a session cookie, headers for a token, authorization for HTTP authentication. Send them in a POST body so they never sit in a web address. Screenshot behind a login

11.Bot-check page

What you see
The screenshot shows a page that says it is checking your browser, a CAPTCHA, or an "Access denied" message.
Why it happens
Some sites put a challenge in front of visitors to decide whether they are human. Cloudflare, for one, describes a challenge as asking the browser to perform a series of checks. An automated browser is exactly what such a check looks for, so it is shown the challenge and the capture records it.[15]
Fix it by hand
Open the page in your normal browser, pass the check, and capture there.
Fix it in an automated capture
If the site is yours, allow your capture tool through in the firewall or bot settings, or capture a staging copy. If it is not yours, ask the owner or use an API they offer. Working around a check can break the site's terms.
In CurlShot
No option removes a bot check, and CurlShot does not try to get around one: the screenshot shows the challenge page. On your own site, allow the service through, or send a secret with headers that your firewall accepts.

When is a page ready for a screenshot? The page load timeline

A web page becomes ready in stages, and a capture taken at each stage fails in its own way. The seven drawings below are one page at seven moments, with what a screenshot taken at that instant would show.

  1. 1. Response starts

    The server has begun to answer and the document starts loading. Playwright calls this commit.[8]

    Captured here: A blank page.

  2. 2. DOMContentLoaded

    The page DOM has been loaded, without waiting for resources to finish loading.[7]

    Captured here: Text and structure, with images still missing.

  3. 3. load

    The page and its dependent resources are loaded: stylesheets, scripts, iframes and images, except those loaded lazily.[7]

    Captured here: Spinners where scripts fetch data, and empty boxes for lazy images.

  4. 4. Network idle

    No network connections for at least 500 ms (Puppeteer's networkidle2 allows 2). A page that polls or streams never gets here.[8][9]

    Captured here: Usually the data, but lazy images below the first screen are still empty.

  5. 5. Fonts ready

    The fonts.ready promise of the document resolves: font loading and the layout after it are complete.[6]

    Captured here: Before this, fallback fonts or invisible text.

  6. 6. Lazy content loaded

    Happens only after something scrolls each lazy image or section near the viewport.[1][3]

    Captured here: Before this, a full-page capture has gaps further down.

  7. 7. Animations settled

    Entrance and scroll-triggered animations have reached their final frame.

    Capture here: The page as a visitor sees it. This is the moment to capture.

The first four moments always come in this order. Fonts, lazy content and animations finish in an order that depends on the page, which is why one waiting rule never fits every site.

Why a fixed delay is the weakest fix

A delay of a few seconds makes every capture slower and still fails on the day the page is slower than the delay. A named element is exact: the capture happens as soon as the content is drawn, and fails loudly if it never is.

Pre-flight checklist before any website screenshot

11 settings to get right before a capture, from the browser window to the final look at the result. Tick them off as you go. The names in code are the CurlShot options that set each one.

Each fix as a CurlShot API option

CurlShot runs the browser for you, so each fix in this guide is one option on the request. The table lists every option the entries name, with its default and what it accepts.

OptionFixesDefaultAllowed
full_pageCut-off pagefalsetrue or false
full_page_scrollBlank image boxestrueUsed with full_page
full_page_max_heightCut-off page20000 px100 to 30000 px
wait_for_selectorSpinners and skeletonsNot setOne CSS selector; it must be visible
wait_untilSpinners, late fontsloadcommit, domcontentloaded, load, networkidle
delayLate fonts, animations0 s0 to 30 s
block_cookie_bannersCookie banner on topfalsetrue or false
hide_selectorsSticky header, any overlayNoneUp to 50 CSS selectors
reduced_motionFrozen mid-animationfalsetrue or false
viewport_deviceWrong layoutNot setA preset name, such as iphone_15_pro
viewport_width, viewport_heightWrong layout1280 x 1024100 to 3840 wide, 100 to 4320 high
viewport_mobileWrong layoutfalsetrue or false
device_scale_factorBlurry text11 to 3
cookies, headers, authorizationLogin wallNot setUp to 50 cookies, up to 50 headers
One request, the common fixes applied
curl "https://curlshot.com/api/v1/screenshot?access_key=YOUR_ACCESS_KEY\
&url=https://example.com\
&full_page=true\
&wait_until=networkidle\
&block_cookie_banners=true\
&hide_selectors=.site-header\
&reduced_motion=true\
&device_scale_factor=2" \
  --output page.png

The request captures the whole page, waits for the network to go quiet, removes the cookie banner and the sticky header, stops animations and doubles the pixel density. More in Waiting and timing and Blocking ads, cookie banners, trackers and chats, or try it without code on Screenshot without cookie banners.

What no option fixes

No option removes a bot check, and CurlShot does not try to get around one. There is also no font-specific wait: a late font is covered by wait_until=networkidle or a short delay.

Screenshot terms, defined

Viewport
The browser window a page is drawn in. Its width decides which layout a responsive page shows.
Full-page screenshot
A capture of the whole scrollable page from top to bottom, not only the part visible in the viewport.
Lazy loading
Holding back an image or a section until the visitor scrolls near it, to make the first load faster.
load event
The browser event fired when the page and its dependent resources have loaded, except those loaded lazily.
Network idle
In Playwright and Puppeteer, the moment when there have been no network connections for at least 500 ms.
Device pixel ratio
Physical screen pixels per CSS pixel: 1 on a classic 96 DPI display, 2 on HiDPI and Retina screens. Also called device scale factor.
Fixed and sticky positioning
CSS that pins an element to the viewport (fixed) or keeps it in view inside its scrolling ancestor (sticky) while the page scrolls.
Fallback font
The font a browser draws text in while the web font it was asked for has not loaded, or has failed to load.

Questions people ask

Why are images blank in my full-page screenshot?

The images are lazy loaded: the browser fetches them only when they come near the viewport, and the load event does not wait for them. Scroll through the whole page before capturing, pause so the images can arrive, then return to the top and take the screenshot.

Why does the header repeat in my full-page screenshot?

The header is a fixed or sticky element, pinned to the viewport. A tool that scrolls, captures each screen and stitches the pieces records it once per piece. Hide the header or set it to position: static before the capture, or use a tool that renders the page in one pass.

How do I take a screenshot without the cookie banner?

By hand, answer the banner first. In an automated capture, set the consent cookie, click the button, block the consent script or hide the banner with CSS. In CurlShot, add block_cookie_banners=true, and hide_selectors for a banner it does not recognise.

Is waiting for network idle enough before a screenshot?

Not always. Network idle means no network connections for at least 500 ms. It does not cover lazy images that need a scroll, fonts still swapping in, or animations, and a page that polls never becomes idle. Waiting for a specific element of the content is more exact.

Why is my screenshot blurry?

It was captured at a device scale factor of 1, one image pixel per CSS pixel, and is being shown on a screen with a device pixel ratio of 2 or more. Capture at scale 2: a 1280 pixel wide viewport then gives a 2560 pixel wide image.

Why does my screenshot show a login page or a CAPTCHA?

An automated browser starts with no session, so a protected page redirects it to the sign-in form; send a session cookie or token with the request. A CAPTCHA or "checking your browser" page is a bot check. CurlShot has no option that removes one: allow the capture tool on your own site, or ask the site's owner.

Sources

  1. [1]MDN Web Docs, <img>: the image embed element (loading attribute). Checked .
  2. [2]MDN Web Docs, Lazy loading. Checked .
  3. [3]MDN Web Docs, Intersection Observer API. Checked .
  4. [4]MDN Web Docs, position (CSS property). Checked .
  5. [5]MDN Web Docs, font-display (@font-face descriptor). Checked .
  6. [6]MDN Web Docs, FontFaceSet: ready property. Checked .
  7. [7]MDN Web Docs, Window: load event. Checked .
  8. [8]Playwright, Page class: goto waitUntil and screenshot options. Checked .
  9. [9]Puppeteer, PuppeteerLifeCycleEvent type. Checked .
  10. [10]Puppeteer, ScreenshotOptions interface. Checked .
  11. [11]MDN Web Docs, prefers-reduced-motion (media feature). Checked .
  12. [12]MDN Web Docs, Viewport meta element. Checked .
  13. [13]MDN Web Docs, Window: devicePixelRatio property. Checked .
  14. [14]Chrome for Developers, Simulate mobile devices with device mode. Checked .
  15. [15]Cloudflare Docs, Challenges. Checked .

Screenshot any page in one step

Paste an address into CurlShot and download the screenshot. A free account gives you 100 screenshots every month. No card needed.