Set full_page=true on a long page and you'll sometimes get back a screenshot with visible gaps —
white or gray bands where an image, a chart, or a below-the-fold section should be. The page looks
fine in a real browser. The capture doesn't match it.
Why this happens
Most modern sites lazy-load anything below the fold: images get a real src only once an
IntersectionObserver decides they're about to enter the viewport, and some frameworks defer whole
sections the same way. A headless browser capturing full_page renders the page at its full
scrolled height in one pass — nothing ever "scrolls past" the lazy content to trigger it, so those
elements are still in their unloaded placeholder state when the screenshot is taken.
The fix isn't a single flag. It's picking the right combination of three parameters, depending on what the page is doing.
wait_until — pick the right definition of "loaded"
wait_until=load (the default) resolves as soon as the initial HTML, CSS, and images referenced in
the markup have loaded — before most JavaScript-driven lazy loading has had a chance to run.
wait_until=networkidle waits until the page has gone quiet on the network for a short stretch,
which is usually enough for lazy-loaded images to have fired their requests. Start here:
&full_page=true&wait_until=networkidle
For pages where "quiet" never quite arrives — infinite scroll feeds, sites with a background
polling request — networkidle can wait longer than you want, or your lazy content still hasn't
loaded because nothing ever scrolled past it. That's what the other two parameters are for.
wait_for — wait for proof the content is there
wait_for takes a CSS selector and holds the capture until that element exists in the DOM. If you
know the last thing on the page that should render — a footer, a "load more" button, the final
card in a grid — target it directly:
&full_page=true&wait_for=footer.site-footer
This is the most reliable option when it's available, because it's the one that verifies the actual outcome instead of guessing at a timing window.
delay — the blunt instrument, used deliberately
Sometimes there's no reliable selector to wait for, or the lazy-loading library needs a scroll
event that a static full-page render never generates. delay adds a fixed pause, in milliseconds,
after whatever wait_until/wait_for condition resolves — up to 5000ms:
&full_page=true&wait_until=networkidle&delay=1500
Reach for delay last. It's the least precise of the three — you're paying for a fixed wait
whether the page needed it or not — but it's the one guaranteed to work when a page's lazy-loading
doesn't expose anything cleaner to hook into.
A reasonable default
For most image-heavy marketing pages, wait_until=networkidle combined with a short delay covers
the common case without needing to know the page's markup in advance:
curl "https://snapurl.app/v1/screenshot?url=https://example.com&full_page=true&wait_until=networkidle&delay=800" \
-H "Authorization: Bearer $SNAPURL_KEY" \
-o shot.png
If you're capturing the same page repeatedly, it's worth spending five minutes finding a wait_for
selector instead — it's faster and it doesn't silently break the day the page gets slower.
See the full parameter reference for everything else /v1/screenshot
accepts.