ScreenshotNeo

BlogHow-to

How to Generate Website Thumbnails for a Static HTML Link Page

Generate durable website thumbnails for static HTML link cards with Playwright or a screenshot API, then wire them into your page with accessible, resilient markup.

By the ScreenshotNeo team4 October 20269 min read

To generate website thumbnails for a static HTML link page, capture each destination URL as an image, save the image in your published assets, and reference it from the matching link card. You can automate capture locally with Playwright or use a hosted screenshot API. For a page you control, local generation is a good fit when you want the files produced alongside your own build process; an API avoids maintaining browser automation in that process.

For most link cards, start with a consistent browser viewport and a compact image format. Store the generated images yourself so they remain available as long as your static page does. The examples below use Playwright with Node.js and include ways to handle capture failures, organize output, and add the results to accessible HTML.

1. Choose a capture method

Method Good fit when Considerations
Playwright You want to generate image files in your project or build workflow. You need to install and run browser automation. You control navigation and screenshot settings.
Hosted screenshot API You prefer to send URLs to a service and receive image data. You need to manage credentials and review the service’s current settings, limits, and retention behavior.

Both routes support screenshot configuration, but the right viewport and refresh schedule depend on your destinations and design. There is no universally correct thumbnail size or regeneration interval. Choose a size that matches your card layout, capture a representative set of pages, and review the results.

2. Generate thumbnails locally with Playwright

Playwright can navigate to a URL and save a screenshot to a file. Its screenshot options also support full-page capture, element screenshots, and image data in a buffer. For link previews, a viewport capture is often a compact starting point; use full-page capture only when the whole page is useful at card size. See the Playwright screenshot guide and Page screenshot API reference.

Install and run a capture script

In an existing Node.js project, install Playwright and its browser. The browser installation command depends on the Playwright package and environment; follow the official installation instructions for your setup.

npm install playwright
npx playwright install chromium

Create generate-thumbnails.mjs. This example uses a list of URL and filename pairs, sets a consistent viewport, waits for the page load event, and writes PNG files into a local directory. Adjust URLs, filenames, viewport, and timeout to suit your pages.

import { chromium } from 'playwright';
import { mkdir } from 'node:fs/promises';

const destinations = [
  { url: 'https://example.com/', file: 'example.png' },
  { url: 'https://developer.mozilla.org/', file: 'mdn.png' },
];

const outputDir = 'public/thumbnails';
await mkdir(outputDir, { recursive: true });

const browser = await chromium.launch({ headless: true });
try {
  const page = await browser.newPage({
    viewport: { width: 1200, height: 800 },
    deviceScaleFactor: 1,
  });

  for (const destination of destinations) {
    try {
      const response = await page.goto(destination.url, {
        waitUntil: 'load',
        timeout: 30_000,
      });

      if (!response || !response.ok()) {
        console.warn(`Skipping ${destination.url}: HTTP ${response?.status() ?? 'no response'}`);
        continue;
      }

      await page.screenshot({
        path: `${outputDir}/${destination.file}`,
        type: 'png',
        fullPage: false,
        animations: 'disabled',
      });
      console.log(`Saved ${destination.file}`);
    } catch (error) {
      console.warn(`Could not capture ${destination.url}: ${error.message}`);
    }
  }
} finally {
  await browser.close();
}

Run it with node generate-thumbnails.mjs. The script continues after an individual destination fails. Confirm the output directory is included in your static site’s published files; public/thumbnails is only an example path, not a requirement of any particular framework.

Choose the capture shape

  • Viewport screenshot: captures the visible browser viewport. Set a fixed width and height for consistent cards.
  • Full page: set fullPage: true when a long-page preview is useful. Shrinking a tall screenshot into a small card can make details hard to distinguish.
  • One element: locate the element with a selector and call locator.screenshot({ path: 'card.png' }) if you want a particular region of a page. Third-party pages may change their markup, so handle a missing selector.
  • Buffer: omit path from page.screenshot() to get image bytes for further processing or custom storage.
  • Output format: Playwright supports PNG and JPEG screenshots; JPEG quality applies when using JPEG. Select a format supported by your rendering pipeline and check the resulting files.
  • Scale and device pixel ratio: choose the browser context’s device scale factor deliberately. A higher pixel density can produce sharper output at the cost of larger images.

For pages that continue loading after the initial navigation, wait for a known selector or a short, bounded delay before capturing. Avoid waiting indefinitely for network idle on pages with ongoing analytics or other requests. If a page uses lazy-loaded images, scrolling through the page before capture may be necessary; test that behavior on the destinations that matter.

3. Put the images in your static HTML

Keep destination data and thumbnail filenames together so the link and image stay paired. Write semantic markup, descriptive alternative text when the screenshot conveys useful information, and a fallback appearance if the image is unavailable.

<ul class="link-cards">
  <li>
    <a class="link-card" href="https://example.com/">
      <img
        src="/thumbnails/example.png"
        alt="Preview of Example's homepage"
        width="600"
        height="400"
        loading="lazy"
      >
      <span>Example</span>
    </a>
  </li>
</ul>

Set width and height to the actual image dimensions or aspect ratio to reduce layout movement while the image loads. If the thumbnail is purely decorative because the link title already conveys all relevant information, use alt="". The destination remains usable as a text link if the image fails.

.link-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1rem;
  list-style: none;
  padding: 0;
}
.link-card {
  display: grid;
  gap: .5rem;
  color: inherit;
  text-decoration: none;
}
.link-card img {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  background: #eee;
  border-radius: .5rem;
}

The CSS crop can hide the edges of a screenshot; inspect cards at their actual size. If the source image dimensions do not match the card ratio, decide whether cropping or letterboxing preserves the useful part of the preview.

4. Hosted screenshot API workflow

A hosted API can return an image for a URL and may offer controls such as output format, dimensions, quality, selectors, excluded elements, dark mode, full-page capture, and caching. For example, OpenGraph.io documents a screenshot endpoint and calls out thumbnail generation for link previews as a use case. Review its current documentation for exact parameter names, availability, and behavior before implementing against it.

For a static site, the general workflow is: send the target URL and capture settings to the endpoint, check the HTTP status and response type, then save the returned bytes into your own published asset directory. Do not assume a returned image URL is permanent. OpenGraph.io documents screenshot URLs that expire after 24 hours, so a durable page should download or cache the output rather than leave a long-lived HTML reference to a temporary URL.

Keep API keys out of public HTML and client-side JavaScript. Run capture from a build script or server-side job, store the credential in the environment, and save successful outputs as static files. Provider parameters and terms can change; verify current pricing, quotas, and retention directly with the provider because they are not established here.

5. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server for developers. Its one-call API can return a screenshot image for a URL; see the ScreenshotNeo API documentation for parameters and response details.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
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 are accepted like a visitor and removed along with 60+ known consent platforms, newsletter popups, and chat widgets before the shot; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits cost nothing, and response headers identify the page verdict and whether it was billed. An MCP server lets AI agents using Claude, Cursor, or any MCP client take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for free and get 1,000 screenshots a month with no card.

6. Reliability, performance, and cost

Make capture jobs resilient

  • Keep a manifest of destination URL, output filename, and capture settings. This makes regeneration repeatable.
  • Use a bounded navigation timeout and report failures per URL so one inaccessible destination does not stop the whole batch.
  • Check status and output before replacing a previously good thumbnail. Retaining the last successful asset is a useful fallback strategy.
  • Review thumbnails after changes to destination pages, since layouts, consent behavior, or selectors can change.
  • Run capture only when the URL list or relevant pages change, or on a schedule appropriate to how frequently the destinations update.

Control time and image size

Each destination requires navigation and rendering, so large batches take longer than a single capture. Reuse a browser process for a batch as in the Playwright example, but isolate pages or contexts if sites interfere with each other’s state. Keep viewport dimensions and device scale consistent. Choose PNG when crisp text or transparency matters; JPEG or WebP may suit photographic page previews when your display pipeline supports them. Compare output quality and file size with your own destinations rather than assuming one setting wins for every page.

Understand cost and retention

Local Playwright avoids a per-request screenshot API charge, but you operate the browser environment and its execution. A hosted service shifts capture operations to the provider and may charge by usage or plan; compare current published terms before choosing. Cache or retain output files yourself when the page needs stable assets. Some screenshot URLs are temporary: the OpenGraph.io documentation says its returned screenshot URLs expire after 24 hours, so copy the image to your own storage for long-lived static pages.

7. Troubleshooting

Symptom Likely cause Fix
Navigation times out The site is slow, unreachable from the capture environment, or keeps network requests open. Check the URL and network access. Use a bounded timeout and a more appropriate readiness condition, such as a specific selector or the load event.
Screenshot is blank or incomplete The page had not rendered its main content, requires interaction, or blocked the capture environment. Wait for a visible content selector, inspect the page state, and handle destinations that require consent or interaction. Do not assume every site permits capture.
Thumbnail shows a consent banner or popup The destination presents an overlay before the content is useful. For local automation, handle the banner or dismiss a known overlay only where appropriate. A screenshot service with consent cleanup may remove supported platforms; verify its documented coverage and settings.
Expected element screenshot fails The selector no longer matches, the element is hidden, or it is outside the expected state. Confirm the selector against the current page, wait for it to appear, and fall back to a viewport screenshot when a destination’s markup changes.
Image is clipped or too tall for the card The capture dimensions and card aspect ratio differ, or full-page mode was used. Choose viewport capture for compact cards, set a consistent aspect ratio, and inspect the crop at display size.
Browser executable is missing The Playwright package is installed without its browser binary. Install the browser required by your environment using Playwright’s documented install command.
Images disappear after deployment The output directory is not published, the path is incorrect, or HTML refers to a temporary hosted URL. Confirm the static build copies the asset directory, check paths relative to the deployed site, and store durable copies of API outputs.
API returns an error or non-image response Credentials, parameters, quota, or destination capture may have failed. Check HTTP status and response headers before saving bytes as an image. Confirm current provider docs and avoid publishing API credentials.

8. Frequently asked questions

Should I capture the whole destination page?

Usually begin with the visible viewport for a compact link card. Use a full-page image when the entire page is meaningful at the rendered size.

How often should thumbnails be regenerated?

There is no universal interval. Regenerate when a destination or your link list changes, or choose a schedule based on how fresh the previews need to be.

Can I use a screenshot URL directly in static HTML?

Only if the provider’s URL lifetime and usage terms meet your needs. For a durable page, save a copy in assets you control.

What should I do when a destination cannot be captured?

Keep the text link working, retain a previous successful thumbnail if available, and record the failure so it can be reviewed or retried.