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:
- Product Hunt -- product screenshots in listing cards
- Bookmark managers (Raindrop.io, Toby) -- visual thumbnails of saved pages
- Resource lists -- "awesome lists" with visual previews
- Portfolio sites -- client work showcased with screenshots
Architecture overview
The setup is straightforward:
- When a new link is added, capture a screenshot via the API
- Resize the screenshot to a thumbnail (e.g., 320px wide)
- Store the thumbnail and associate it with the link
- 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.jsCost planning
Each thumbnail uses one screenshot from your quota:
- Small directory (50 links): 50 screenshots/month for refreshes = Free plan
- Medium directory (500 links): 500 screenshots/month = Free plan
- Large directory (5,000+ links): Pro plan at $29/month
Tips for better thumbnails
- Use ad blocking: Add
block_ads=truefor cleaner captures without cookie popups - Wait for content: Use
wait=2000for sites with heavy JavaScript - Consider WebP: WebP thumbnails are 30-50% smaller than PNG with no visible quality loss
- Cache aggressively: Store thumbnails locally and only refresh periodically
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.