Why Cloudinary Website Screenshots Miss Lazy-Loaded Images
Cloudinary lazy loading can leave below-the-fold images blank in a screenshot. Learn how to trigger loading, wait for rendering, and diagnose missing assets.
Direct answer: Cloudinary lazy loading defers image loading until an image is visible or near the viewport. If a screenshot is captured before below-the-fold images have been triggered and rendered, those areas can show blanks, placeholders, or stale content. Scroll through the page to trigger lazy loading, then wait for the relevant image requests and rendering to finish before capturing. There is no universal wait duration that guarantees every page is ready.
This explanation combines Cloudinary’s documented loading behavior with reader reports of missing images in full-page captures; no screenshot tool was tested. A screenshot tool’s behavior depends on how it scrolls, captures, and waits.
1. Why lazy-loaded images are missing
Cloudinary describes lazy loading as waiting to download images until they are visible or the user scrolls near them. Its JavaScript image transformation documentation shows a lazyload() plugin with configurable rootMargin and threshold. In the documented example, a threshold of 0.25 starts loading when 25% of an image is visible; these are example settings, not defaults for every Cloudinary integration. Cloudinary JavaScript Image Transformations.
A full-page screenshot may cover the entire document without making every section visible to the page’s lazy-loading code. If an image has not crossed its visibility trigger, its request may not have started when the screenshot is taken. A page can also show a placeholder before the final image is ready, so seeing something in the image area does not necessarily mean the full asset has loaded.
Cloudinary’s JavaScript documentation supports combining lazy loading and placeholders. It also recommends setting image width and height to reduce layout shifts as images load. See the plugin documentation.
2. Trigger images before a full-page capture
- Open the page at the intended viewport size. Responsive layouts may select different assets or position content differently at different widths.
- Let initial scripts and layout settle. This gives client-side code time to create image elements or assign their image URLs.
- Scroll through the content in overlapping increments. Move each section into or near the viewport so its lazy-loading trigger can fire. Avoid jumping straight to the bottom if intermediate content must load.
- Wait for image requests and rendering after each relevant scroll. Use observable completion signals where available, such as the image becoming complete and having a nonzero natural width. A fixed delay is only a heuristic; page behavior and network conditions vary.
- Check the required capture area, then take the screenshot. If images are still missing, determine whether they exist in the document and whether their final URLs were assigned.
For pages with infinite scrolling, scrolling may append new content as well as trigger images already in the document. Continue until the intended content has appeared, then trigger and wait for images in those newly appended sections too.
3. Runnable Playwright example in JavaScript
This example scrolls through the document in steps, waits for image elements that have been assigned a source to finish loading (or fail), and then saves a full-page screenshot. It uses a bounded per-image wait so one broken image does not block the whole run indefinitely. Adjust the step size and timeout for the site you are capturing. Install Playwright and its browser before running: npm install playwright and npx playwright install chromium.
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({ viewport: { width: 1365, height: 900 } });
try {
await page.goto('https://example.com', { waitUntil: 'domcontentloaded', timeout: 60000 });
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
await page.evaluate(async () => {
const step = Math.max(300, Math.floor(window.innerHeight * 0.75));
const pause = ms => new Promise(resolve => setTimeout(resolve, ms));
const maxScroll = Math.max(0, document.documentElement.scrollHeight - window.innerHeight);
for (let y = 0; y <= maxScroll; y += step) {
window.scrollTo(0, y);
await pause(150);
}
window.scrollTo(0, maxScroll);
await pause(150);
const images = Array.from(document.images).filter(img => img.currentSrc || img.src);
await Promise.all(images.map(img => {
if (img.complete) return Promise.resolve();
return new Promise(resolve => {
const done = () => resolve();
img.addEventListener('load', done, { once: true });
img.addEventListener('error', done, { once: true });
setTimeout(done, 15000);
});
}));
});
await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
await browser.close();
}
})();
Replace https://example.com with the page to capture. The example waits for image load or error events, but it cannot guarantee that every site uses ordinary image elements or that an image which fires load is visually correct. CSS background images, canvas content, client-side replacement after the scan, and infinite-scroll content may need site-specific handling. Avoid treating networkidle as proof that all lazy images have loaded: deferred requests may not begin until scrolling, and some pages maintain ongoing network activity.
4. Diagnose a missing image
- Image area is empty and no request appears: scroll the element into view and inspect whether the page assigns a source URL only after visibility. Check for lazy-loading script errors.
- A placeholder appears, but not the final image: wait for the final resource, not just the placeholder. Cloudinary documents placeholders as a way to show a lightweight image while the full image downloads.
- The image element exists but has no usable URL: inspect
src,srcset, andcurrentSrcafter scrolling. The site may populate these values through client-side code. - The image has a URL but fails: inspect the browser’s network and console output for a failed request, access restriction, or script error. This identifies a page-loading issue separately from a screenshot timing issue.
- Images load but sections shift or are clipped: check whether the page reserves image dimensions. Cloudinary recommends width and height attributes to reduce layout shifts.
- Only newly appended sections are missing: the page may use infinite scrolling. Scroll far enough to append the intended sections, then repeat the image-trigger and wait steps.
- Only the top image is important for page performance: do not lazy-load the Largest Contentful Paint (LCP) image. Cloudinary’s guidance also recommends measuring the LCP effect of a low-quality image placeholder implementation. Cloudinary LQIP and Lazy Loading Best Practices.
5. Cloudinary and page implementation details
Lazy-load triggers and placeholders
Cloudinary’s JavaScript image transformation docs describe a plugin sequence example using lazyload(), responsive(), accessibility(), and placeholder(); plugins can be omitted when not needed. The documented lazy-loading controls include a root margin and threshold. A larger trigger region can start loading before an image is actually visible, but it does not remove the need for a capture workflow to account for final resource loading.
Responsive images and delivery size
Responsive image handling affects which asset a browser requests. Cloudinary’s responsive-images documentation compares approaches and notes that JavaScript-based responsive methods can delay the start of image loading, while client-hints delivery can avoid that delay in supported Chromium-based browsers; a sizes attribute is still required for that approach. Choose based on integration needs and browser support. A responsive-image technique by itself does not trigger deferred images for a screenshot. Cloudinary responsive images.
Cloudinary recommends delivering images near their final display size instead of downloading oversized assets and shrinking them in the browser. Its optimization documentation notes that f_auto may not return an expected format when a delivery URL is opened directly, because the browser may not send the same Accept header. That direct-URL preview issue is distinct from a lazy-loaded image that was never requested. Cloudinary image optimization.
WordPress scope
Cloudinary’s WordPress plugin documentation says lazy loading is enabled by default as part of setup and that users can configure the behavior and placeholder. It also says transformations used for lazy loading count against usage, and that the plugin does not support lazy loading for videos. These statements apply to the documented WordPress plugin, not every Cloudinary setup. Cloudinary WordPress integration.
6. Search crawling is related, but different
Google’s guidance says lazy-loaded content should load when it becomes visible and warns against requiring user actions such as scrolling or clicking to make content available to its crawler. Google Search does not interact with a page by scrolling or clicking. That is a discoverability constraint, separate from screenshot automation, which can deliberately scroll the page. Google Search Central: Fix lazy-loaded website content.
7. Performance, reliability, and cost considerations
- Capture time: scrolling, waiting for newly triggered requests, and decoding images adds time. Use a deliberate step size and wait for relevant completion signals instead of applying one large arbitrary delay.
- Network and memory: a full-page capture may cause many image assets to load. Capture at the viewport and scale you need, and avoid loading unrelated infinite-scroll content.
- Layout reliability: reserved image dimensions reduce shifts while images arrive. Capture after the page reaches the layout state you intend to preserve.
- Failure handling: distinguish a completed image from a failed image. A bounded wait and error logging help prevent one stalled request from consuming the whole job.
- Page performance: lazy loading can defer off-screen work, but do not delay the LCP image. Measure placeholder effects on LCP for the implementation in use; Cloudinary gives no universal numerical cutoff.
- Capture cost: the research sources establish no cost figures for Cloudinary screenshot workflows or automation tools. Account for browser runtime, network transfer, and any image transformation usage in the services actually used.
8. Or skip the browser setup
Use ScreenshotNeo for a website screenshot with one GET request. Its capture workflow removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed; response headers report the page verdict and billing status. 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. Every feature is on every plan. See the ScreenshotNeo API docs.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Create a free account for 1,000 screenshots a month, with no card.
9. FAQ
Does Cloudinary lazy loading always cause screenshot images to disappear?
No. It defers loading until its trigger condition is met. Whether a capture misses an image depends on the page and capture workflow.
Is a placeholder enough to count an image as loaded?
No. A placeholder may appear while the final image is still downloading. Wait for the final resource if the screenshot must show it.
What wait duration should I use?
There is no source-backed universal duration. Prefer image-specific load or error signals with a timeout, and tune the timeout to the site and network conditions.
Will Google scroll the page to load lazy images?
Google’s guidance says not to depend on user actions such as scrolling or clicking to make content available to Search. See its lazy-loading guidance.


