ScreenshotNeo

BlogHow-to

How to Make Website Thumbnails for a SaaS Products Directory

Build consistent website thumbnails for a SaaS directory: capture, store, refresh, and serve previews with a reliable workflow.

By the ScreenshotNeo team4 October 202613 min read

To make website thumbnails for a SaaS products directory, render each product’s public homepage at the same browser viewport, save the resulting image under a durable URL, and display it inside a consistently sized card. For a first-screen preview, capture the viewport rather than the full page. Automate captures for a large catalog, and replace an existing thumbnail only after a refresh succeeds.

This guide covers the image contract, capture choices, a runnable self-managed Python workflow, storage and refresh behavior, production concerns, troubleshooting, and a hosted API option. Provider controls and behaviors cited below come from their documentation; they have not been independently benchmarked.

1. Define the thumbnail your directory needs

Decide how the thumbnail will be presented before capturing sites. A consistent image contract keeps cards comparable and avoids recapturing the catalog when the layout changes.

Decision Practical starting point Why it matters
Aspect ratio Match the card container, such as 4:3 or 16:9. A mismatch can crop important content or add empty space.
Viewport Choose one desktop or mobile viewport for every listing. Different viewport sizes can produce very different page layouts. OpenGraph.io documents presets including 375 × 812, 1024 × 768, 1366 × 768, and 1920 × 1080. The right one depends on your card and audience. OpenGraph.io screenshot documentation
Capture area Use the visible viewport for a recognizable first-screen preview. Full-page captures include below-the-fold content and are usually less legible when reduced to a small card.
Format Use WebP or JPEG when supported and image size matters; use PNG when lossless output or transparency is needed. Confirm browser and storage delivery support, and choose dimensions and compression based on actual card rendering.
Durability Store the image in your own object storage or media system and save its stable key or URL. A capture provider’s response URL may be temporary. OpenGraph.io says its example screenshot URLs expire after 24 hours, so download or cache the image when it must remain available. OpenGraph.io documentation

Record the chosen viewport, format, crop policy, and refresh policy as application configuration. A change to these settings can alter every image, so version them if the directory needs reproducible previews.

2. Choose how to capture the directory

For a production directory or recurring capture pipeline, ScreenshotNeo is the first service to consider: it removes cookie banners, popups, and chat widgets before capture, bills only clean shots, and its lowest paid plan is $5 for 3,000 shots.

Method Good fit Documented capabilities and tradeoffs
Hosted screenshot API A directory that needs automated URL-to-image capture without operating its own browser workers. OpenGraph.io documents dimensions, formats, viewport and full-page capture, and selector controls. Check current pricing, limits, retention, URL expiration, and behavior on protected sites before depending on a provider. OpenGraph.io docs
Self-managed browser capture A builder who wants control over execution and storage. shot-scraper documentation describes batch URL jobs configured in YAML, selector capture, and pixel-density controls. You arrange execution, storage, retries, and scheduling.
Scheduled screenshot service A catalog that needs recurring previews. ScreenshotEngine describes daily or weekly recaptures and retaining the last successful image if a refresh fails. Verify current behavior, supported destinations, limits, and terms before relying on it. ScreenshotEngine
Dynamic OG-image generator Designed promotional cards rather than faithful captures of live sites. CaptureAPI documents template-based dynamic Open Graph image generation. That produces a designed card from content; it does not by itself ensure an accurate screenshot of each homepage. CaptureAPI

These are different workflows, not a verified ranking of third-party vendors. The cited sources describe vendor features, not comparative performance or cost. Test representative sites and confirm current terms before choosing.

3. Build a self-managed capture workflow

The following Python example uses Playwright to render a URL in Chromium and save a viewport screenshot. It provides a starting point for a small job runner. For a large catalog, run it in a worker queue with bounded concurrency rather than launching an unbounded number of browser instances.

Install the browser automation dependency

python -m pip install playwright
python -m playwright install chromium

Capture one site

import asyncio
from pathlib import Path
from playwright.async_api import async_playwright

async def capture(url: str, output: str) -> None:
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        page = await browser.new_page(
            viewport={"width": 1366, "height": 768},
            device_scale_factor=1,
        )
        try:
            response = await page.goto(
                url,
                wait_until="networkidle",
                timeout=45_000,
            )
            if response is None:
                raise RuntimeError("Navigation did not return a main-document response")
            if response.status >= 400:
                raise RuntimeError(f"Target returned HTTP {response.status}")
            await page.screenshot(path=output, type="webp")
        finally:
            await browser.close()

asyncio.run(capture("https://stripe.com", "stripe.webp"))

Save the script as capture.py and run python capture.py. The example deliberately has a timeout and checks the main document response. Sites that keep long-polling or analytics connections open may never reach networkidle; in that case, use wait_until="domcontentloaded" and wait for a known page element or a short fixed delay appropriate to your use case. A generic screenshot cannot guarantee that every site has finished rendering.

Capture a selected element or full page

If a homepage has a useful preview region inside a larger page, wait for a stable selector and capture that element. If the directory needs a complete record rather than a small card preview, use a full-page capture deliberately.

# Replace navigation and screenshot lines inside capture() as needed:
await page.goto(url, wait_until="domcontentloaded", timeout=45_000)
await page.locator("main").wait_for(state="visible", timeout=15_000)
await page.locator("main").screenshot(path=output, type="webp")

# For a full-page image instead:
await page.screenshot(path=output, type="webp", full_page=True)

The selector must exist and be visible. A missing selector should be treated as a capture failure, not silently accepted as a valid thumbnail. Full-page images can be very tall and expensive to transfer or store; resize or create a card-sized derivative for display if retaining the full capture.

4. Process a URL list safely

Keep product metadata and capture status in your database. Give each listing a stable identifier and store a key to its image rather than deriving identity from a mutable homepage URL.

from dataclasses import dataclass

@dataclass
class Listing:
    slug: str
    url: str

listings = [
    Listing("stripe", "https://stripe.com"),
    Listing("linear", "https://linear.app"),
]

# A production worker should process these with a small bounded concurrency,
# write to a temporary object key, validate the image, then atomically update
# the listing's current thumbnail key only after the new object is stored.

For each job, track at least the source URL, capture configuration version, start and completion time, outcome, resulting object key, and error category. This lets you distinguish a source-site problem from a storage or worker problem and retry only what failed.

Do not accept arbitrary user-submitted URLs into a browser worker without controls. Restrict schemes to HTTP and HTTPS, resolve and check destination addresses, block access to private and link-local networks, and re-check redirects. Otherwise, a directory listing could cause your worker to request internal services. Also set page, download, and total-job timeouts, and cap output dimensions and file size.

5. Store and serve durable thumbnails

  1. Capture to a temporary local path or temporary object key.
  2. Check that the output exists, has a supported image type, and is not empty or implausibly small.
  3. Optionally generate a card-sized derivative to reduce transfer size.
  4. Upload the new image to durable storage under a versioned key.
  5. Update the listing’s active image reference only after the upload succeeds.
  6. Retain the previous image until the new one is confirmed usable; clean up old versions under your retention policy.

This order avoids breaking a working card when capture or upload fails. If a provider returns a temporary URL, fetch and persist the bytes before treating the result as the listing’s permanent image. CaptureAPI documents a 400 × 300 WebP directory-thumbnail example, but that is an example configuration, not a universal standard or independently validated optimum. CaptureAPI

Serve thumbnails through your image host or object storage with appropriate caching headers. Keep the original capture separate from any smaller card derivative if you may need to regenerate display sizes without recapturing the source site.

6. Refresh previews without losing the last good image

SaaS homepages change, so recapture on a schedule suited to the directory. Refresh less often for stable catalogs and more often when homepage changes are important to users. Avoid synchronized bursts: spread jobs across the schedule and enforce a concurrency limit.

  • Start a refresh in the background and keep serving the active image.
  • Write the replacement under a new object key.
  • Validate capture and upload before changing the active reference.
  • On failure, retain the last successful image and record the failure for retry or review.
  • Use backoff for transient network and rate-limit failures; do not retry permanent errors indefinitely.

ScreenshotEngine describes daily or weekly refreshes and keeping the last successful screenshot when a refresh fails; verify its actual current behavior before depending on that service behavior. ScreenshotEngine

7. API examples for a hosted capture

A hosted API can remove the need to maintain browser installation and capture workers. Check its current authentication, accepted parameters, output behavior, limits, and retention policy. The examples below show the general form of a request to OpenGraph.io’s documented endpoint; use your own API key and confirm the current endpoint and parameter requirements in its docs. The temporary URL behavior described by that documentation means production code should persist the returned image bytes when long-term availability is needed. OpenGraph.io API documentation

cURL

curl -G "https://opengraph.io/api/1.1/site/https%3A%2F%2Fstripe.com" \
  --data-urlencode "app_id=YOUR_API_KEY" \
  --data-urlencode "use_screenshot=true" \
  --data-urlencode "dimensions=1366x768"

The documented API returns metadata that includes a screenshot URL; download that image and save it to durable storage rather than assuming the URL is permanent. Consult the provider docs for current response fields and options.

Python

import requests

api_url = "https://opengraph.io/api/1.1/site/https%3A%2F%2Fstripe.com"
response = requests.get(
    api_url,
    params={
        "app_id": "YOUR_API_KEY",
        "use_screenshot": "true",
        "dimensions": "1366x768",
    },
    timeout=60,
)
response.raise_for_status()
data = response.json()
screenshot_url = data["screenshotUrl"]
image_response = requests.get(screenshot_url, timeout=60)
image_response.raise_for_status()
with open("stripe.webp", "wb") as image_file:
    image_file.write(image_response.content)

Response field names and accepted parameters can change; confirm them against the provider’s current documentation before production use. Add application-level retry and validation appropriate to your job runner.

Node.js

const apiUrl = new URL(
  "https://opengraph.io/api/1.1/site/https%3A%2F%2Fstripe.com"
);
apiUrl.searchParams.set("app_id", "YOUR_API_KEY");
apiUrl.searchParams.set("use_screenshot", "true");
apiUrl.searchParams.set("dimensions", "1366x768");

const apiResponse = await fetch(apiUrl);
if (!apiResponse.ok) {
  throw new Error(`Capture API returned HTTP ${apiResponse.status}`);
}
const data = await apiResponse.json();
const imageResponse = await fetch(data.screenshotUrl);
if (!imageResponse.ok) {
  throw new Error(`Image download returned HTTP ${imageResponse.status}`);
}
const imageBytes = new Uint8Array(await imageResponse.arrayBuffer());
await import("node:fs/promises").then(({ writeFile }) =>
  writeFile("stripe.webp", imageBytes)
);

Use a supported Node.js version with global fetch, or provide a fetch implementation. Add request timeouts and durable object storage in a production worker. API authentication may appear in query parameters for this documented example, so avoid logging full request URLs containing credentials.

8. ScreenshotNeo: one call for a clean directory preview

ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. Its API returns an image or PDF from one GET request. For directory cards, use a consistent viewport and image format, then save the returned bytes to your own durable storage. See the ScreenshotNeo API documentation for current parameters and response details.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Python

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)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' }); const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

ScreenshotNeo can accept cookie or consent banners like a visitor and remove more than 60 known consent platforms, newsletter popups, and chat widgets before the shot; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and responses identify the page verdict and billing status in X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. Plans include 1,000 free shots each month with no card, then paid plans from $5 for 3,000 shots; every feature is on every plan. Explore ScreenshotNeo.

Sign up for 1,000 free screenshots a month, with no card required.

9. Performance, reliability, and cost

Performance

  • Use a modest, fixed viewport and card-appropriate output dimensions. Larger dimensions and full-page captures create more pixels to encode, transfer, and store.
  • Choose the smallest wait condition that produces a representative capture. Waiting for every network request can stall on analytics or long-lived connections; waiting too briefly can capture a skeleton or incomplete hero.
  • Use bounded concurrency. Each browser process consumes memory and CPU, and target sites may rate-limit or slow repeated requests.
  • Cache captures by a key that includes the source URL and capture configuration. A changed viewport, selector, or format should result in a distinct cache entry.

Reliability

  • Expect sites to redirect, block automation, load slowly, show maintenance pages, or change their layout.
  • Distinguish a legitimate page response from a useful image. An HTTP success can still show a blank shell, a bot check, or a broken page.
  • Keep the last good thumbnail until the next one passes validation and storage succeeds.
  • Set per-navigation and total-job timeouts. Record failure categories and retry transient issues with backoff.
  • Some sites require authentication or restrict capture. Do not bypass access controls; use only pages you are authorized to capture and follow applicable site terms.

Cost

For a self-managed workflow, account for browser-worker compute, storage, image delivery, and engineering/operations time. For a hosted API or refresh service, compare current plan limits, billed events, refresh frequency, retention, and overage terms against catalog size. The research sources do not establish which vendor is cheapest or most reliable for a particular directory. ScreenshotNeo’s stated pricing is 1,000 shots per month free with no card; Starter $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan. Confirm current terms at ScreenshotNeo.

10. Troubleshooting common capture problems

Symptom Likely cause Fix
Blank or mostly empty image The page had not rendered, a script failed, or the site served a bot check. Inspect the page response and screenshot outcome. Wait for a known visible element, try a reasonable delay, and keep the previous image if the result is unusable.
Navigation timeout The site is slow or keeps network connections open. Use a bounded timeout and a less restrictive readiness condition such as DOM content loaded, then wait for the element that matters. Do not remove timeouts.
Cookie dialog covers the preview The site presents a consent dialog that obscures the page. Where appropriate, configure a documented consent-handling option or exclude a known overlay. Avoid hiding page content broadly, since it can remove the actual product preview.
Selected element not found The selector is incorrect, page structure changed, or the target is not in the initial DOM. Inspect the page, update the selector, and wait for it with a finite timeout. Treat absence as a failed capture.
Thumbnails look inconsistent Viewport, device scale, wait behavior, or crop differs across jobs. Centralize these settings and version the capture configuration. Use a single viewport and consistent output processing.
Image URL stops working later The provider’s screenshot URL was temporary or the image was not copied to durable storage. Download the image bytes after capture, upload them to your storage, and save your own stable object key. OpenGraph.io says its example screenshot URLs expire after 24 hours.
Refresh replaces a good image with a broken one The pipeline updates the active reference before validating the replacement. Upload to a new key, validate it, then switch the listing reference. Retain the last successful image on any failure.
Provider request is rejected Invalid credentials, malformed target URL, unsupported parameter, or quota/rate limit. Check the current API docs and response body, validate URL encoding and credentials, and apply backoff for transient limits.
Capture worker can reach unexpected hosts Untrusted URLs can redirect or resolve to internal addresses. Allow only HTTP(S), block private and link-local destination addresses, and re-check redirects and DNS results before navigation.

11. Frequently asked questions

Should a directory thumbnail show a whole homepage?

Usually it should show the first viewport so users can recognize the product’s current landing page. Use full-page capture when the directory’s purpose is review, archival, or page auditing.

Can I use a thumbnail as a designed promotional card?

Yes, but a designed card is a different asset from a faithful live-site screenshot. A template-based OG image generator is suited to branded composition from listing data; use page capture when the preview should represent the actual website.

How often should I refresh the images?

Choose a cadence based on how quickly the sites change and how current the directory must feel. Schedule refreshes with enough capacity for retries and keep the last good image until a replacement is ready.

Can every site be captured accurately?

No capture workflow can guarantee that every target renders the same way. Bot checks, access restrictions, client-side failures, geolocation, and design changes can affect the result. Record failures and make the directory resilient to stale previews.

Sources and evidence limits

These sources are vendor documentation and product descriptions, not independent benchmarks. Check current pricing, rate limits, retention, terms, and behavior on representative sites before selecting a provider.