Menu
Sign up

Website thumbnails from a URL: capture, size, format and caching, measured

Make website thumbnails for directories and link cards: capture at desktop width, shrink after, pick WebP, and cache with a signed URL. Sizes measured.

Published November 8, 2026 · 5 min read

A website thumbnail is a small picture of a page's first screen: the preview in a link directory, a "sites we built" grid, a bookmark manager, a CMS field that shows where a link goes. Making one sounds like one screenshot, but three choices decide whether it looks right and loads fast: the window you capture in, the format, and where the shrinking and caching happen. This page goes through each with numbers we measured.

How we measured. On 2026-11-08 we captured the home pages of python.org, w3.org, nodejs.org, debian.org, rust-lang.org and mozilla.org through our screenshot API from our development stack (Free-plan captures, which carry a small badge), in PNG, JPEG and WebP, then shrank them with Pillow 11.1. Each capture took 4.8–8.9 s, most of it the sites' own loading.

1. Capture at desktop width, shrink afterwards

The tempting shortcut is to ask for a 400 px wide screenshot. Websites see a 400 px window as a phone and switch to their mobile layout: python.org captured at 400×250 showed a full-width menu (Close, Python, PSF, Docs, PyPI…) and nothing of its home page. Its responsive layout is doing what the viewport meta tag asks for.

So capture at the width people use the site at — our default viewport is 1280×800 — and scale the picture down. The viewport's height sets the crop: 1280×800 is 16:10; for 4:3 cards send "viewport_height": 960; for a square, 1280×1280.

2. WebP, and how much quality

Sizes at 1280×800, first screen only, cookie banners removed:

Site PNG JPEG (q 85) WebP (q 85) WebP (q 60)
python.org 133 KB 105 KB 56 KB 38 KB
w3.org 157 KB 111 KB 69 KB 50 KB
nodejs.org 256 KB 112 KB 62 KB 43 KB
debian.org 593 KB 158 KB 109 KB 69 KB
rust-lang.org 107 KB 116 KB 68 KB 49 KB
mozilla.org 58 KB 77 KB 35 KB 27 KB

WebP was the smallest for every page, at about half of JPEG. PNG is lossless and swings wildly: flat colours compress well (mozilla.org), photos and gradients do not (debian.org's banner, 593 KB). quality (1–100, default 85) applies to JPEG and WebP; 60 is fine for a picture that will be shown at a third of its size.

3. Shrink once, not in every browser

A 1280×800 picture in <img width="400"> looks right, but every visitor downloads the full 56 KB. Shrunk to 400×250 first, the same WebP was 8.7 KB for python.org, 8.5 KB for nodejs.org and 13.6 KB for debian.org — six times smaller. With Pillow:

from PIL import Image

img = Image.open("python.org.webp")       # the 1280×800 capture
img.thumbnail((400, 400))                 # keeps the aspect ratio: 400×250
img.save("python.org-400.webp", "WEBP", quality=80)

Do it where your images are processed anyway: when the link is added to your directory, in a build step, or through an image CDN that resizes by URL. For retina screens, keep an 800 px version too and offer both with srcset (MDN: img). Our API does not shrink captures for you: scale goes up (1, 2, 3 for retina), never down. Pillow's thumbnail() is documented in the Pillow reference.

4. Caching: a signed URL in the page

For thumbnails shown straight from our side, put a signed /s/ URL in the <img>. It is a normal GET that anyone can load, with your options in the query and a signature so nobody can change them:

import { createHmac } from 'node:crypto';

const enc = (v) => encodeURIComponent(String(v))
  .replace(/[!'()*]/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase());

export function thumbnailUrl(pageUrl) {
  const params = { url: pageUrl, block_cookie_banners: '1', viewport_height: '960' };
  const path = `/s/${process.env.SHOT_ACCOUNT_ID}.webp`;
  const query = Object.keys(params).sort()
    .map((name) => `${enc(name)}=${enc(params[name])}`).join('&');
  const s = createHmac('sha256', process.env.SHOT_SIGNING_SECRET)
    .update(`${path}?${query}`).digest('hex').slice(0, 32);
  return `https://shareglint.com${path}?${query}&s=${s}`;
}
// <img src={thumbnailUrl('https://www.w3.org/')} width="400" height="300" loading="lazy" alt="w3.org">

What that URL did for w3.org at 1280×960:

Request Answer Time
First 302 to the new capture (81 KB WebP) 7.1 s
Second 302 to the same file 0.08 s
Third 302 to the same file 0.11 s
viewport_height changed to 961 by hand 403 —

The first visitor waits for the capture. To spare them, request the URL once on your server when the link is added; every later view is a redirect to a stored file. Within 24 hours the same options never capture again, so a page viewed a thousand times a day costs one capture a day. The signature is computed with your signing secret on your server — never in browser code; signed image URLs explains the scheme, and the JavaScript guide shows the same function checked against our verifier.

loading="lazy" keeps a long grid from loading thumbnails nobody scrolls to, and the width/height attributes keep the layout from jumping while they load.

Many URLs at once

A directory with 2,000 links needs 2,000 captures once, then only new links. Send them from a script with a few requests in parallel and webhooks for the results; bulk website screenshots has the script and what concurrency did to the total time. A viewport capture costs 1 render of the quota; repeats within 24 hours are free (pricing).

A thumbnail as a card

For link-preview cards with the site's name and description next to the picture, render the capture into a template instead of showing it raw: site-card takes a title, a description and an image, and browser-frame puts the screenshot in a browser window. The card then has a fixed layout whatever the site looks like.

Limits

  • Public pages only. No logins, no intranet, no pages behind a bot wall.
  • First screen only in a thumbnail; for the whole page see full-page screenshots.
  • A day old at most, not live. A site that changes every hour shows the version captured first that day.
  • Some sites show pop-ups other than cookie banners (subscription offers, sign-in boxes); hide_selectors removes them once you know their selector (cookie banners and other pop-ups).

FAQ

How do I get a thumbnail image of a website from its URL?

Take a screenshot of the page's first screen at desktop width (1280 px), then shrink it to the size you display, for example 400 px wide. With our API, a signed /s/ URL in an img tag captures the page on the first request and serves the stored file afterwards; resize it in your own pipeline or image CDN.

Why not just capture the page at thumbnail size?

Because a narrow window is a phone to the website. Captured with a 400 px viewport, python.org showed its mobile menu instead of its home page. Capture at the width people browse at and scale the picture down.

Which format is best for website thumbnails?

WebP. On six home pages captured at 1280×800, WebP at quality 85 was 35–109 KB against 77–158 KB for JPEG and 58–593 KB for PNG; at quality 60 it was 27–69 KB. Shrunk to 400×250, the WebP thumbnails were 8–14 KB.

How often are thumbnails refreshed?

The same URL with the same options returns the stored capture for 24 hours; the first request after that captures the page again. So a thumbnail is at most a day old, and you pay for at most one capture per page per day however many people view it.