Every team that needs website screenshots eventually writes the same code: spin up headless Chrome, wait for the page, take a picture, hope it looks right. It works in development. In production it becomes one of the most annoying pieces of infrastructure you own.
Here is what actually breaks, and what I ended up doing about it.
1. The page is never ready when you think it is
waitUntil: 'networkidle' is a guess. Single page apps fetch data after the first paint, images load lazily as they scroll into view, and fonts swap a second later. You get a screenshot of a skeleton loader.
What works better is a combination: wait for network idle, then wait for a specific selector, then wait a fixed extra beat for fonts and images inside the viewport. Chromium also exposes Page.captureScreenshot with captureBeyondViewport, which avoids scrolling tricks that trigger more lazy loading.
2. Cookie banners ruin half the captures
Every second site shows a consent wall. A screenshot with a giant modal covering the content is useless for a thumbnail, a link preview or a PDF report.
The reliable approach is to inject a small CSS and JS payload before capture: hide known consent containers by common selectors and attributes, and block the requests from known consent and ad domains so the modal never renders in the first place. Blocking at the network layer is far more stable than clicking "Accept" with a selector that changes every month.
3. Memory leaks end your process at 3am
A Chromium instance that renders thousands of pages will grow until the kernel kills it. The fixes are boring but necessary:
- one browser process, many short lived contexts
- a hard cap on concurrent pages
- recycle the browser after N captures
- per request timeouts that actually close the page
4. The API surface is the easy part
Once capture works, the product questions start. Do you return the image inline or a URL? How do you handle a 40 second page? What about 500 URLs in one request?
Batch endpoints and signed webhooks solve the second and third. Signed URLs solve the first without leaking your API key into HTML.
What I built instead
I got tired of maintaining this per project, so I wrapped it into a small API: SnapRender. One request turns a URL, raw HTML or Markdown into a PNG, JPEG, WebP or PDF, rendered in real Chrome, with device presets, full page capture, ad and cookie banner removal, and content extraction to Markdown.
curl "https://app.snap-render.com/v1/screenshot?url=https://example.com&format=png&full_page=true" \
-H "X-API-Key: YOUR_API_KEY" -o example.png
The free plan is 200 captures a month with no card, and every feature is on every plan. There is also a hosted MCP server if you want an agent to take screenshots and read pages directly.
If you are still running your own fleet, the four problems above are the ones to plan for. If you would rather not, that is the problem I am solving.
Top comments (0)