Why Screenshot Automation Breaks on Single-Page Apps
I wasted most of a Friday trying to figure out why our screenshot service was capturing blank pages. The URL worked fine in a browser. The API returned a 200. But the image was just a white rectangle with a loading spinner.
The site was a React SPA. The HTML that came back from the server was basically an empty <div id="root">. Everything rendered client-side, and our screenshot tool was capturing the page before React finished hydrating.
The wait-for-selector trick
Most headless browser tools let you specify an element to wait for before capturing. Instead of screenshotting immediately after navigation, you tell it "wait until this CSS selector exists in the DOM."
For React apps, something like wait_for: ".main-content" or whatever wrapper your app uses. For Vue, same idea. The point is — don't trust load or DOMContentLoaded events. Those fire when the initial HTML is done, which for SPAs means almost nothing is visible yet.
The tricky part
Some SPAs load content in stages. The shell renders first, then the data comes from an API call, then the actual content fills in. If you wait for the shell element, you capture the skeleton loader. If you wait for the data element, you might wait forever if the API is slow.
What's worked for me: wait for a specific element AND add a small delay (500ms-1s). Not elegant, but reliable. The alternative is waitUntil: "networkidle0" in Puppeteer, which waits until there are no network requests for 500ms. Works most of the time, but some apps have websocket connections or polling that never go idle.
When it still doesn't work
Had one case where a site used client-side auth. The page rendered a login form first, then redirected after checking a cookie. Our screenshots always got the login page because the headless browser had no session cookies.
Fix: pass cookies or auth headers in the screenshot request if your tool supports it. Most API-based screenshot services let you set custom headers. If yours doesn't, that's probably a sign to look at other options.
Not every screenshot tool handles SPAs well. The ones built on top of Puppeteer or Playwright generally do. The ones using server-side rendering approaches... don't.