·SavePage Team

Capturing Screenshots of JavaScript-Heavy Pages

javascriptsparendering

Modern web applications load content dynamically with JavaScript. React, Vue, Angular, and Next.js apps often show a loading spinner or blank page before their content renders. Capturing a screenshot at the wrong moment gives you an empty shell instead of the actual content.

The timing problem

When a browser navigates to a URL, several things happen in sequence:

  1. HTML is downloaded and parsed
  2. CSS stylesheets are loaded
  3. JavaScript bundles are downloaded and executed
  4. The JavaScript framework initializes and renders components
  5. API calls are made for dynamic data
  6. The data arrives and the UI updates
  7. Images and media load
  8. The page is visually complete

The load event fires after step 2-3, but the page may not be visually complete until step 7-8. For server-rendered pages, step 2 is often sufficient. For client-rendered SPAs, you need to wait until step 6-7.

Strategies

Fixed delay

The simplest approach: wait a fixed number of milliseconds after the page loads.

/v1/?url=https://spa.example.com&delay=3000

Pros: Simple, works for most cases. Cons: Too short misses content; too long wastes time.

A good starting point is 2000-3000ms for typical SPAs. Increase to 5000ms for pages with heavy API calls.

Network idle

The networkidle strategy waits until there are no network requests for 500ms. This usually indicates that all API calls have completed and the page is done loading data.

SavePage.io uses network idle detection automatically. The delay parameter adds additional wait time after network idle, which is useful for pages that trigger rendering after data arrives.

DOM stability

Some screenshot tools wait for the DOM to stop changing. If no new elements are added or modified for a threshold period, the page is considered stable.

This is harder to implement but catches cases where network idle fires before the UI has finished rendering the received data.

Common pitfalls

Infinite polling. Some pages poll for updates (chat apps, dashboards, live feeds). The network never truly goes idle. In these cases, a fixed delay is more reliable than network-based detection.

Lazy-loaded images below the fold. Images that load on scroll are not visible in a viewport screenshot unless you scroll to them first. For full-page captures, the renderer scrolls through the page to trigger all lazy loading.

Authentication redirects. If the page redirects to a login page, you capture the login form instead of the content. Check for authentication requirements before capturing.

Cookie consent banners. Many pages show a cookie consent banner that overlays the content. The banner appears after JavaScript loads, so it shows up in screenshots. There is no universal solution; some teams dismiss the banner with custom JavaScript injection, while others accept it as part of the page's actual appearance.

Loading skeletons. Design systems often show skeleton loaders (gray placeholder shapes) before content renders. A screenshot taken too early captures these placeholders. Increase the delay until the actual content replaces the skeletons.

Testing your delay

The best way to find the right delay is empirical testing:

  1. Capture the page with delay=0
  2. Capture with delay=1000
  3. Capture with delay=3000
  4. Capture with delay=5000
  5. Compare the results

When consecutive delay values produce the same image, you have found the minimum necessary delay. Add a small buffer (500-1000ms) for reliability and use that value.

For pages you capture regularly, this calibration only needs to happen once. The delay value is stable unless the page's architecture changes.