Blog/Tutorial

Build a Link Directory with Screenshot Previews

Link directories, bookmark managers, and resource lists are 10x more engaging with visual previews. Here is how to auto-generate them with a screenshot API.

March 14, 2026|8 min read

Why screenshot previews matter for directories

A list of URLs is boring. A grid of website thumbnails is engaging. Studies show that visual previews increase click-through rates by 30-50% compared to text-only links.

Examples of sites that use this pattern:

Architecture overview

The setup is straightforward:

  1. When a new link is added, capture a screenshot via the API
  2. Resize the screenshot to a thumbnail (e.g., 320px wide)
  3. Store the thumbnail and associate it with the link
  4. Periodically refresh screenshots to keep them current

Step 1: Capture and store thumbnails

const fetch = require('node-fetch');
const fs = require('fs');
const path = require('path');

const API_KEY = process.env.SCREENSHOT_API_KEY;
const API_URL = 'https://screenshotapi-api-production.up.railway.app';

async function generateThumbnail(url, outputDir = './thumbnails') {
  // Create a URL-safe filename
  const filename = url.replace(/https?:\/\//, '').replace(/[^a-z0-9]/gi, '_') + '.png';
  const filepath = path.join(outputDir, filename);

  // Capture screenshot with auto-resize to 320px width
  const apiUrl = new URL(`${API_URL}/v1/screenshot`);
  apiUrl.searchParams.set('url', url);
  apiUrl.searchParams.set('width', '1280');
  apiUrl.searchParams.set('height', '800');
  apiUrl.searchParams.set('format', 'png');
  apiUrl.searchParams.set('output_width', '320');  // Auto-resize to thumbnail

  const response = await fetch(apiUrl.toString(), {
    headers: { 'Authorization': `Bearer ${API_KEY}` },
  });

  if (!response.ok) {
    throw new Error(`Failed to capture ${url}: ${response.status}`);
  }

  fs.mkdirSync(outputDir, { recursive: true });
  const buffer = await response.buffer();
  fs.writeFileSync(filepath, buffer);

  return { url, filepath, filename };
}

// Example usage
async function main() {
  const links = [
    'https://github.com',
    'https://stripe.com',
    'https://vercel.com',
    'https://tailwindcss.com',
  ];

  for (const link of links) {
    const result = await generateThumbnail(link);
    console.log(`Generated: ${result.filename}`);
  }
}

main();

Step 2: Build the directory page

Here is a simple React component that displays links with their thumbnails:

function LinkDirectory({ links }) {
  return (
    <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
      {links.map(link => (
        <a
          key={link.url}
          href={link.url}
          target="_blank"
          rel="noopener noreferrer"
          className="group block rounded-xl border overflow-hidden hover:shadow-lg transition"
        >
          <div className="aspect-video bg-surface-200 overflow-hidden">
            <img
              src={link.thumbnailUrl}
              alt={`Screenshot of ${link.title}`}
              className="w-full h-full object-cover group-hover:scale-105 transition-transform"
              loading="lazy"
            />
          </div>
          <div className="p-4">
            <h3 className="font-semibold">{link.title}</h3>
            <p className="text-sm text-slate-500">{link.description}</p>
          </div>
        </a>
      ))}
    </div>
  );
}

Step 3: Refresh screenshots periodically

Websites change over time. Set up a cron job to refresh thumbnails weekly or monthly:

async function refreshAllThumbnails(links) {
  console.log(`Refreshing ${links.length} thumbnails...`);

  for (const link of links) {
    try {
      await generateThumbnail(link.url);
      console.log(`Refreshed: ${link.url}`);

      // Rate limiting: wait 1 second between requests
      await new Promise(resolve => setTimeout(resolve, 1000));
    } catch (err) {
      console.error(`Failed: ${link.url} - ${err.message}`);
    }
  }
}

// Run weekly via cron
// 0 0 * * 0 node refresh-thumbnails.js

Cost planning

Each thumbnail uses one screenshot from your quota:

Tips for better thumbnails

Get started

Try capturing a thumbnail in the playground. Set output_width to 320 to see the thumbnail size. No signup needed for the demo.

Try It in the Playground

Related articles