How to Make Website Thumbnail Previews for Affiliate Resource Pages
Build consistent website thumbnails for affiliate resource pages with Playwright or a screenshot API, and learn how to handle disclosures and image rights.
To make website thumbnail previews for an affiliate resource page, capture each destination in a consistent browser viewport, crop and export it in the format your cards use, then check the actual display size for legibility. Use a viewport screenshot when the card should identify the site at a glance, an element screenshot for a specific feature, and a full-page screenshot only when readers can inspect it at a larger size. Before publishing, check the destination site’s terms and any affiliate-program restrictions, and put a clear commission disclosure close to monetized links.
For a few destinations, manual browser capture may be enough. For a repeatable build pipeline, use browser automation such as Playwright. For URL-driven capture without maintaining a browser service, consider a hosted screenshot API. ScreenshotNeo is the first API to try for this workflow: it removes cookie banners, popups, and chat widgets before capture, bills only clean shots, and its paid plan starts at $5 for 3,000 screenshots.
1. Decide what each preview needs to show
A thumbnail is a visual label for a destination, not necessarily a miniature copy of the whole page. Decide on the image’s job before choosing capture settings.
| Preview goal | Capture | Use it when |
|---|---|---|
| Identify the site’s first screen | Viewport screenshot | The card is small and should communicate the destination quickly. |
| Show one product, tool, or page area | Element or clipped screenshot | A specific section is more recognizable than the page header. |
| Let readers inspect a long page | Full-page screenshot | The image will be displayed large enough to remain useful. |
Full-page captures often become difficult to read when reduced to a card. Test the intended image at its final display size. Keep the viewport, crop, format, and displayed dimensions consistent across the resource list; consistency makes the grid easier to scan.
2. Capture thumbnails with Playwright
Playwright can save a page screenshot to a file or return image bytes for further processing. Its screenshot options include image type, clipping, full-page capture, and scale. The examples below use Node.js and Chromium. See the Playwright screenshot guide and Page screenshot API.
Install
npm init -y
npm install playwright
npx playwright install chromium
Runnable capture script
Save as capture.mjs. This script reads URLs from the command line, uses one viewport for each, waits for the page to reach a useful state, and writes WebP thumbnails. It closes the browser even if a capture fails.
import { chromium } from 'playwright';
import { mkdir } from 'node:fs/promises';
import { createHash } from 'node:crypto';
const urls = process.argv.slice(2);
if (urls.length === 0) {
console.error('Usage: node capture.mjs https://example.com [https://another.example]');
process.exit(2);
}
const outputDir = 'thumbnails';
await mkdir(outputDir, { recursive: true });
const browser = await chromium.launch({ headless: true });
try {
const context = await browser.newContext({
viewport: { width: 1280, height: 800 },
deviceScaleFactor: 1,
colorScheme: 'light',
});
for (const rawUrl of urls) {
let url;
try {
url = new URL(rawUrl);
if (!['http:', 'https:'].includes(url.protocol)) throw new Error('Only HTTP and HTTPS URLs are supported');
} catch (error) {
console.error(`Skipping ${rawUrl}: ${error.message}`);
continue;
}
const page = await context.newPage();
try {
const response = await page.goto(url.href, { waitUntil: 'domcontentloaded', timeout: 30000 });
if (!response) console.warn(`${url.href}: navigation returned no main-resource response`);
await page.locator('body').waitFor({ state: 'visible', timeout: 10000 });
// Give client-rendered layout a short settling period; tune for the target sites.
await page.waitForTimeout(800);
const name = createHash('sha256').update(url.href).digest('hex').slice(0, 16);
await page.screenshot({
path: `${outputDir}/${name}.webp`,
type: 'webp',
quality: 82,
fullPage: false,
animations: 'disabled',
});
console.log(`${url.href} -> ${outputDir}/${name}.webp`);
} catch (error) {
console.error(`Capture failed for ${url.href}: ${error.message}`);
} finally {
await page.close();
}
}
await context.close();
} finally {
await browser.close();
}
Run it with node capture.mjs https://stripe.com https://playwright.dev. Change the example URLs to the destinations on your resource page. The script uses WebP output; PNG and JPEG are also supported. Choose the format your publishing system and browsers support.
Capture one element or the full page
For a selector-based capture, wait for the element and screenshot its bounding element:
const target = page.locator('main .product-card').first();
await target.waitFor({ state: 'visible', timeout: 10000 });
await target.screenshot({ path: 'element.png', type: 'png' });
For a full-page capture, replace the screenshot option with fullPage: true. For a fixed crop, provide a clip rectangle in page CSS pixels:
await page.screenshot({
path: 'crop.png',
type: 'png',
clip: { x: 0, y: 0, width: 1000, height: 650 },
});
Element and clip screenshots can still look different across destinations because layouts vary. Check for missing content, sticky headers, overlays, and responsive breakpoints before using the image.
3. Tune the capture for repeatable results
Wait for useful content
Navigation completion and visual readiness are different. A page may render its main layout before images, fonts, or client-side content appear. The example waits for DOM content, a visible body, and a short settling delay. For a known target, a selector wait is more precise:
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.locator('main h1').waitFor({ state: 'visible', timeout: 15000 });
Use networkidle only when it fits the page. Analytics, long polling, and other persistent requests can prevent network idle from occurring. A site-specific selector or a bounded delay can be more reliable. If lazy-loaded images matter, scroll the page in stages before a full-page capture, then return to the top; some pages load those images only near the viewport.
Keep viewport and scale stable
Set the viewport explicitly for every context. A responsive site can show a different navigation, hero image, or column layout at each width. Playwright’s scale option can capture CSS-pixel output or device-pixel output; device scale affects image pixel dimensions and file size. Decide once and apply it to all cards. If output dimensions must match a fixed card ratio, crop consistently after capture or use a consistent clip.
Choose image type and quality
- PNG: useful when crisp edges or lossless output matter; may produce larger files.
- JPEG: useful for photographic pages where lossy compression is acceptable.
- WebP: a compact web image option supported by Playwright; confirm it fits your delivery requirements.
Compression settings and final image dimensions affect file size and visual quality. Inspect text and logos at the actual card size, and avoid enlarging a low-resolution capture in the page layout.
4. Choose a workflow for your URL volume
| Workflow | Best fit | Trade-offs to check |
|---|---|---|
| Manual browser capture | A small, occasional set of pages | Repeat viewport and crop carefully; manual updates take attention. |
| Playwright or similar automation | Custom browser behavior, selector logic, or an existing build pipeline | You maintain browser installation, execution, retries, and output storage. |
| Hosted screenshot API | A URL-driven workflow that needs image output without operating a capture browser | Check current options, price, privacy and retention terms, reliability, and usage rights with the provider. |
OpenGraph.io documents a URL-based screenshot endpoint with controls for format, quality, full-page behavior, dimensions, selectors, and excluded selectors. Its documentation establishes those controls, not price, uptime, affiliate terms, or comparative quality. Review current vendor terms directly before choosing. OpenGraph.io screenshot documentation.
5. Publish thumbnails responsibly
Check rights and program rules
A screenshot is not automatically cleared for commercial use. Check the destination site’s current terms, copyright and trademark rules, and any affiliate program’s restrictions on copied content. Permission or an example concerning one site’s screenshot does not grant rights to another site’s page, logos, or imagery. The W3C permission example is specific to W3C material; Amazon’s policies separately restrict use and redistribution of Amazon Program Content in applicable contexts. Read the current rules that apply to your use.
Disclose affiliate relationships close to recommendations
Make the financial relationship clear and conspicuous near the recommendation or link. The FTC says “affiliate link” by itself may not explain that you receive a commission. Its example disclosure is: “I get commissions for purchases made through links in this post.” Use language that accurately describes your relationship. If the Amazon Associates rules apply to your site and region, Amazon requires the identification statement “As an Amazon Associate I earn from qualifying purchases.” See the FTC endorsement guidance and Amazon Associates help.
6. Or skip the browser setup
ScreenshotNeo accepts a URL in one GET request and returns an image or PDF. Its API documentation describes the available parameters. For a resource-page thumbnail, request an image format and save the response as a file.
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,
)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
The Node example uses Bun’s file writer. In Node.js without Bun, save the response body with the filesystem API:
import { writeFile } from 'node:fs/promises';
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 writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before the shot; each of those cleanup steps can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000, and every feature is on every plan. Sign up for 1,000 free screenshots a month, with no card required.
7. Troubleshooting
| Problem | Likely cause | Fix |
|---|---|---|
| Screenshot is blank or mostly empty | The page has not rendered its client-side content, the URL redirected, or access is blocked. | Wait for a visible page-specific selector, inspect the final URL and response, and check whether the destination requires authentication or blocks automated access. |
| Images or fonts are missing | Resources load after the initial navigation or only when scrolled into view. | Wait for a relevant image or selector; for full-page work, scroll in stages to trigger lazy loading. Use a bounded timeout. |
| Capture hangs on network idle | The page keeps network connections open for analytics or live updates. | Use domcontentloaded plus a target selector or a short bounded delay instead of relying on network idle. |
| Thumbnail crops the wrong region | The viewport, CSS layout, or selected element differs between sites. | Standardize viewport settings; inspect the element’s bounding box; use a deliberate clip and test the result at card size. |
| Text is too small in a full-page preview | The long image is scaled down to fit a card. | Use a first-screen or element capture, or provide a larger view users can open. |
| Output format is unexpected | The requested type or file extension does not match. | Set type explicitly in Playwright, or request a supported format from the API; align the saved extension with the returned format. |
| Different captures look inconsistent | Viewport, color scheme, device scale, animation state, or capture wait varies. | Set those context options uniformly, disable animations where appropriate, and use the same readiness rule for comparable pages. |
| A site returns a challenge or denial | The destination applies bot protection or access restrictions. | Respect the site’s access rules; do not treat a challenge screen as a valid thumbnail. Check the provider response verdict when using an API. |
8. Performance, reliability, and cost
- Browser resources: launching a browser has setup and memory costs. Reuse one browser process and create or close pages per URL, as in the example. Limit concurrency to what the host can support.
- Timeouts and retries: bound navigation and selector waits. Retry transient network failures with a small cap and backoff; avoid retry loops for persistent access denials or invalid URLs.
- Output storage: use deterministic filenames or a URL-to-file manifest so reruns can update the right card. Keep generated files out of public publishing paths until reviewed.
- Cache policy: if destinations change infrequently, cache captures and refresh them on a schedule or when links change. Account for stale previews if a page redesigns.
- Hosted capture costs: compare current price, billing rules, cache behavior, and included volume directly with each provider. Research cited here does not establish commercial performance or current provider pricing. ScreenshotNeo’s stated plans are 1,000 free monthly shots with no card, then $5 for 3,000, $15 for 15,000, $39 for 60,000, $99 for 250,000, or $249 for 1,000,000; yearly billing gives two months free.
- Failure handling: record the URL, capture time, output path, and error for each item. A failed or blocked destination should be reviewed instead of silently published as an empty card.
9. FAQ
Should I show screenshots of affiliate products or just the destination site?
Use the view that identifies the destination and fits your permissions. A page screenshot may include third-party marks or content, so check the relevant site’s and affiliate program’s current rules.
Does an affiliate link disclosure replace permission to use a screenshot?
No. Disclosure explains the commercial relationship to readers; it does not grant rights to copy a site’s content or marks.
Can I use the same thumbnail for desktop and mobile layouts?
You can, but a responsive destination may look substantially different at different viewport widths. Choose and consistently use the view that best represents the destination in your resource page.
Do screenshots improve affiliate clicks?
The cited research does not establish a measurable effect on clicks or conversions. Treat thumbnails as a way to help readers recognize destinations, and assess your own page results if you need evidence of business impact.


