ScreenshotNeo

BlogHow-to

How to Generate Retina-Resolution Website Thumbnails for a Directory

Generate consistent, high-density website thumbnails with Playwright or shot-scraper. Choose the right capture area, scale, format, and batch workflow.

By the ScreenshotNeo team4 October 20268 min read

To generate retina-resolution website thumbnails, choose a fixed CSS viewport, then capture at a device scale factor greater than 1. With Playwright and a device scale factor of 2, a 1200 × 750 CSS-pixel viewport produces an image of 2400 × 1500 pixels when the screenshot is captured at device-pixel resolution. Choose viewport, full-page, or element capture according to what each directory card should show, and keep the browser environment and capture settings consistent across URLs.

For a directory, a viewport capture is usually the simplest way to get uniform framing. Retina resolution increases pixel density; it does not decide the crop, image format, or final size shown in your directory. Those are separate choices.

1. Choose the capture area and dimensions

Start with the thumbnail’s intended aspect ratio and the content it should communicate. Set a CSS viewport to that shape before navigating. At device scale factor 2, the output width and height are each twice the CSS viewport dimensions; at scale factor 3, they are three times as large. Confirm the actual dimensions from a sample before running the full directory.

Capture type Use it when Tradeoff
Viewport Cards should show a consistent above-the-fold sample. Content below the initial viewport is excluded.
Full page The page as a whole matters more than a uniform crop. Images can be very tall and may need a separate crop or resize for the directory grid.
Element A particular hero, preview, or other selected region is the intended subject. The selector must exist and identify the right element on each site.

Playwright supports these capture modes, PNG, JPEG, and WebP output, and screenshot buffers or paths. Choose the output format based on your directory’s compatibility, transparency needs, and storage constraints. There is no universal best format for every page; compare representative captures in your actual delivery pipeline. See the Playwright screenshot API.

2. Generate a batch with Playwright

This Node.js example reads a JSON array of URLs, uses one browser and a fixed viewport, captures each page at device scale factor 2, and writes a mapping of URLs to output files. It saves one screenshot per URL and continues when a navigation fails. It requires Node.js, Playwright, and a urls.json file such as ["https://example.com", "https://example.org"].

npm install playwright
npx playwright install chromium
// capture.mjs
import { chromium } from 'playwright';
import { readFile, mkdir, writeFile } from 'node:fs/promises';

const urls = JSON.parse(await readFile('urls.json', 'utf8'));
if (!Array.isArray(urls) || !urls.every((url) => typeof url === 'string')) {
  throw new Error('urls.json must be an array of URL strings');
}

const outputDir = 'thumbnails';
await mkdir(outputDir, { recursive: true });
const browser = await chromium.launch({ headless: true });
const results = [];

try {
  for (let index = 0; index < urls.length; index += 1) {
    const url = urls[index];
    const filename = `${String(index + 1).padStart(4, '0')}.png`;
    const page = await browser.newPage({
      viewport: { width: 1200, height: 750 },
      deviceScaleFactor: 2,
    });

    try {
      const response = await page.goto(url, {
        waitUntil: 'domcontentloaded',
        timeout: 45000,
      });
      // Allow a short rendering interval after navigation. Change this policy
      // if your target pages need a specific selector or a longer wait.
      await page.waitForTimeout(1000);
      await page.screenshot({
        path: `${outputDir}/${filename}`,
        type: 'png',
        fullPage: false,
        animations: 'disabled',
      });
      results.push({ url, file: filename, status: response?.status() ?? null });
      console.log(`Saved ${url} -> ${filename}`);
    } catch (error) {
      results.push({ url, file: null, error: String(error) });
      console.error(`Failed ${url}: ${error.message}`);
    } finally {
      await page.close();
    }
  }
} finally {
  await browser.close();
  await writeFile('thumbnails-manifest.json', JSON.stringify(results, null, 2));
}

Run it with node capture.mjs. A 1200 × 750 CSS viewport at scale 2 should yield a 2400 × 1500 image. Confirm that with your image-processing tool or file metadata; browser settings and capture choices can affect results.

Wait for the content you need

The example waits for the DOM to be parsed and then pauses briefly. That is a practical starting point, not a guarantee that every site has finished rendering. If a page renders its hero asynchronously, replace the fixed pause with a selector wait that reflects the content you need:

await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 45000 });
await page.locator('main h1').waitFor({ state: 'visible', timeout: 15000 });
await page.screenshot({ path: filename, animations: 'disabled' });

Pick a selector that is reasonably consistent across the URLs in your directory. For a mixed set of sites, a single selector may not work; use per-site rules, a bounded delay, or a documented fallback. Use networkidle only when it is appropriate for the sites you capture: pages with persistent network activity may never reach an idle state.

Capture a full page or a selected element

To include the full scrollable page, set fullPage: true. This can create tall images, so make a deliberate decision about how your directory will crop or resize them.

await page.screenshot({ path: filename, fullPage: true, animations: 'disabled' });

To capture one element, locate it and call its screenshot method. The element must be present and visible; handle a missing or ambiguous match as a per-URL failure.

const hero = page.locator('main .hero').first();
await hero.waitFor({ state: 'visible', timeout: 15000 });
await hero.screenshot({ path: filename, animations: 'disabled' });

For device emulation and viewport configuration, see Playwright emulation. For practical guidance on keeping screenshot output comparable, see Playwright visual comparisons.

3. Use shot-scraper from the command line

If your batch is a shell-driven workflow, shot-scraper documents a --retina shortcut for device scale factor 2 and supports scale factor 3. These are shot-scraper-specific options; check the installed version’s command help and documentation before incorporating flags into a job.

shot-scraper https://example.com -o example.png --retina

For a scale factor of 3, use the tool’s device scale factor option as documented by the installed version. See the shot-scraper documentation. Playwright is a programmable library suited to custom navigation, error handling, and per-site rules; shot-scraper offers a command-line workflow. The documented options do not establish a speed, cost, or maintenance winner, so choose based on how your job is integrated and the controls it needs.

4. Keep the batch consistent and recoverable

  1. Fix the viewport and scale. Use the same CSS viewport and device scale factor for every capture unless the directory intentionally has multiple thumbnail profiles.
  2. Keep the environment stable. Pin the browser version and run captures in a consistent operating-system and headless configuration. Rendering can vary by host OS, browser version, settings, hardware, and headless mode.
  3. Define a page-state policy. Decide what counts as ready: a selector, a bounded delay, or another condition. Disable animations where possible and account for timestamps, rotating ads, and other changing content.
  4. Use deterministic filenames and a manifest. Keep the original URL next to each output filename. The example uses sequence numbers so URL characters cannot create invalid paths; the manifest preserves the mapping.
  5. Validate a sample first. Check output dimensions, crop, sharpness, and whether the important content is visible. Then process the remaining URLs using the same configuration.
  6. Make failures visible. Record navigation and capture errors per URL, keep the rest of the batch running, and retry failures separately instead of silently treating missing files as success.

Retina captures use more pixels than CSS-pixel captures. That increases the image data your batch must write and store, though the exact storage and processing impact depends on page content and format. Keep the high-density original if you need it for later resizing; otherwise, create a normalized derivative sized for the directory’s actual display. The research does not establish a universal thumbnail dimension, throughput, or compression ratio.

5. Common problems and fixes

Problem Likely cause Fix
Image dimensions match the CSS viewport instead of doubling. The capture used CSS-pixel output, or the configured device scale factor did not apply. Set the context’s deviceScaleFactor before navigation and use device-pixel screenshot output where the API provides that option. Inspect a sample’s actual dimensions.
Some thumbnails are blank or missing content. Navigation or client-side rendering did not finish before the shot, or navigation failed. Wait for a meaningful selector or use a bounded delay; record response status and errors. Add a per-site readiness rule where needed.
Batch job hangs on a few URLs. A page is slow, unresponsive, or keeps network connections open. Set navigation and selector timeouts, avoid unbounded waits, catch errors per URL, and continue with the rest of the list.
Images have inconsistent framing. Viewport dimensions differ, or full-page captures vary in height. Use the same viewport and capture type. If using full-page shots, normalize them to the directory’s target aspect ratio in a separate image-processing step.
Visual differences appear between runs. Browser or host changes, animations, timestamps, ads, or other dynamic page content. Keep browser and host settings stable, disable animations, and wait for a consistent state. Hide or avoid volatile content when it is appropriate for the intended thumbnail.
Element capture times out or captures the wrong region. The selector is absent, matches multiple elements, or is not visible yet. Use a site-specific selector, wait for visibility, choose the intended match explicitly, and log the URL when the selector fails.
Files overwrite each other or are hard to map back. Filenames derived directly from URLs collide or contain unsuitable characters. Use stable IDs or sequence numbers and write a manifest that maps every filename to its source URL.
Output files are unexpectedly large. High device scale, full-page height, or format/content choices increase pixel count or file size. Capture only the needed area, choose a scale that meets the display-density requirement, and compare PNG, JPEG, or WebP on representative pages.

6. Or skip the browser setup

ScreenshotNeo is a website screenshot API: send a URL and receive an image or PDF. Its retina scale and other capture options are documented in the ScreenshotNeo API docs. This one-call example saves the response body as a WebP file:

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

Or use Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Or use 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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(({ writeFile }) => writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));

Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000 screenshots. Sign up for free and get 1,000 screenshots a month with no card.

7. FAQ

Do I need a retina display to generate retina-resolution thumbnails?

No. The browser capture’s device scale factor controls the output pixel density; the display used to run the batch does not need to be a retina display.

Does a 2× screenshot mean the directory should display it at twice the size?

No. It means the capture contains twice as many pixels in each dimension as its CSS viewport. Your directory can display that image at the intended CSS size.

Should I capture the full page for every directory entry?

Only if a full-page preview serves the directory’s purpose. A viewport capture is easier to keep consistent; a full-page image may need cropping or resizing.

Is there one best image format for all website thumbnails?

No. Test the formats your delivery pipeline supports against representative pages and choose based on compatibility, transparency, and storage needs.