DEV Community

Mack
Mack

Posted on

Automate Website Thumbnails for Your Link Aggregator or Directory

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();
}
Enter fullscreen mode Exit fullscreen mode

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 });
});
Enter fullscreen mode Exit fullscreen mode

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 });
  }
}
Enter fullscreen mode Exit fullscreen mode

Display Tricks

Lazy loading — Use loading="lazy" or Intersection Observer.

<img src="${link.thumb}" loading="lazy" alt="${link.title}" />
Enter fullscreen mode Exit fullscreen mode

Fallback placeholders — Not every site screenshots cleanly:

function thumbUrl(link) {
  return link.thumb || `/placeholders/${getDomainColor(link.url)}.svg`;
}
Enter fullscreen mode Exit fullscreen mode

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 });
Enter fullscreen mode Exit fullscreen mode

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)