If you're building a link aggregator, directory, or bookmark manager, you've probably wondered: how do I show a preview thumbnail for every link without manually screenshotting them?
The answer: automate it with a screenshot API.
The Problem
You have a database of URLs. Users submit links, or you curate them. For each one, you want a visual thumbnail — something like what Google, Pinterest, or link-in-bio tools show.
Manually capturing these? Not scalable. Browser extensions? Fragile. You need a programmatic solution.
The Solution: Screenshot on Ingest
Here's the pattern: every time a new URL enters your system, fire off a screenshot request and store the result.
async function captureThumb(url) {
const response = await fetch('https://api.rendly.dev/v1/screenshot', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
url,
width: 1280,
height: 720,
format: 'webp',
quality: 80
})
});
if (!response.ok) throw new Error(`Screenshot failed: ${response.status}`);
return response.json();
}
Handling It at Scale
For a directory with hundreds or thousands of links, you don't want to block on each screenshot. Use a queue:
async function onNewLink(link) {
await db.links.create({ url: link.url, title: link.title, thumb: null });
await thumbnailQueue.add({ linkId: link.id, url: link.url });
}
thumbnailQueue.process(async (job) => {
const { linkId, url } = job.data;
const result = await captureThumb(url);
await db.links.update(linkId, { thumb: result.url });
});
Refreshing Stale Thumbnails
Websites change. A thumbnail from 6 months ago might be outdated. Set up a periodic refresh:
async function refreshStaleThumbs() {
const staleLinks = await db.links.where(
'thumb_updated_at < ?',
new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
);
for (const link of staleLinks) {
await thumbnailQueue.add({ linkId: link.id, url: link.url, refresh: true });
}
}
Display Tricks
Lazy loading — Use loading="lazy" or Intersection Observer.
<img src="${link.thumb}" loading="lazy" alt="${link.title}" />
Fallback placeholders — Not every site screenshots cleanly:
function thumbUrl(link) {
return link.thumb || `/placeholders/${getDomainColor(link.url)}.svg`;
}
Responsive sizes — Serve different sizes for different contexts:
const listThumb = await captureThumb(url, { width: 640, height: 360 });
const detailThumb = await captureThumb(url, { width: 1280, height: 720 });
Real-World Use Cases
- Link aggregators (like Hacker News, but with previews)
- Bookmark managers (Raindrop.io, Pocket-style)
- Web directories (curated lists of tools, resources)
- Portfolio sites (showing project thumbnails)
- Competitive analysis dashboards
Cost Considerations
At scale:
- 1,000 links × monthly refresh = 1,000 screenshots/month
- 10,000 links × monthly refresh = 10,000 screenshots/month
Most screenshot APIs (including Rendly) have tiered pricing that makes this affordable.
Wrapping Up
Automating thumbnails turns a "nice to have" into a reliable feature. The pattern is simple: capture on ingest, queue for scale, refresh periodically. Your users get a richer experience, and you never manually screenshot anything again.
Top comments (0)