How to Make Website Previews for a Travel Blog Directory
Build consistent, accessible homepage previews for a travel blog directory with a practical capture workflow, runnable code, and a refresh plan.
To make website previews for a travel blog directory, capture each blog’s public homepage at the same viewport size, save the resulting image in storage you control, and display it in a consistent card alongside the blog’s name, description, and a normal text link. For a short list that rarely changes, manual captures are enough. For many listings or regular refreshes, automate captures and keep a fallback image for sites that cannot be captured.
This guide uses a local Playwright script as the do-it-yourself browser method. It also shows how to use a hosted screenshot API. The preview is a visual aid: keep the destination and its description in real text so the listing remains understandable if the image fails to load.
1. Decide what the preview should represent
A screenshot and a page’s social-sharing image answer different questions:
- Homepage screenshot: shows how the site currently looks in a browser at a chosen viewport. Use this when the directory promises a preview of the blog itself.
- Open Graph image: the image a site provides for sharing or previews. It may be more curated, but it does not necessarily show the current homepage. Google documents
og:imageas one way to indicate a preferred image for a page in its image guidance.
Choose one representation for the directory, or label the difference clearly. Do not silently mix social images and screenshots in the same grid as if they showed the same thing.
2. Set consistent capture and card dimensions
Pick a viewport and image ratio before capturing the directory. A desktop viewport gives a familiar site snapshot; a narrower viewport can better match a mobile-first directory. Use the same choice for every listing so card heights and crops stay predictable.
A viewport screenshot is usually a compact visual hint. A full-page screenshot preserves more of the page, but it can be very tall and may need substantial cropping to work in a card. If readers need a longer visual record, link to or open a larger version rather than shrinking a full-page image into a tiny card.
| Decision | Practical starting point |
|---|---|
| Capture area | One viewport for directory cards; full page only when the extra page content is useful. |
| Aspect ratio | Choose a stable landscape card ratio and crop or resize every preview to match. |
| Format | Use a format supported by your image pipeline and target browsers; compare output size and visual quality before choosing. |
| Refresh policy | Record the capture date and decide how often a changed homepage should be recaptured. |
3. Capture previews manually or automate them
Manual capture for a small, stable directory
- Open each public homepage in a browser at the same viewport dimensions.
- Wait for the main page content and images to appear. If a consent notice blocks the page, decide whether the directory should show it or dismiss it consistently.
- Capture the viewport, crop to the chosen card ratio, and export an appropriately sized image.
- Save the file under a stable name, such as a directory listing ID, and record the capture date.
- Review the resulting grid at desktop and mobile widths; recrop any image where the important content is cut off.
Automate local captures with Playwright
When you have many URLs or need repeatable refreshes, a browser automation script can capture each page using the same viewport. The example below writes PNG files into a local previews directory. It intentionally records failures instead of stopping the whole batch. It does not attempt to bypass bot checks or site access controls.
mkdir travel-previews
cd travel-previews
npm init -y
npm install playwright
npx playwright install chromium
Create capture.mjs:
import { chromium } from 'playwright';
import { mkdir, writeFile } from 'node:fs/promises';
const sites = [
{ id: 'example-travel-blog', url: 'https://example.com/' },
{ id: 'another-travel-blog', url: 'https://www.example.org/' },
];
const outputDir = new URL('./previews/', import.meta.url);
await mkdir(outputDir, { recursive: true });
const browser = await chromium.launch({ headless: true });
const results = [];
try {
for (const site of sites) {
const page = await browser.newPage({
viewport: { width: 1280, height: 800 },
deviceScaleFactor: 1,
});
try {
const response = await page.goto(site.url, {
waitUntil: 'domcontentloaded',
timeout: 45000,
});
// Allow a short, bounded time for client-rendered content to appear.
await page.waitForTimeout(1500);
const image = await page.screenshot({
type: 'png',
fullPage: false,
animations: 'disabled',
});
await writeFile(new URL(`${site.id}.png`, outputDir), image);
results.push({
id: site.id,
url: site.url,
status: response?.status() ?? null,
capturedAt: new Date().toISOString(),
error: null,
});
} catch (error) {
results.push({
id: site.id,
url: site.url,
status: null,
capturedAt: new Date().toISOString(),
error: String(error),
});
} finally {
await page.close();
}
}
} finally {
await browser.close();
}
await writeFile(
new URL('capture-manifest.json', outputDir),
JSON.stringify(results, null, 2),
);
console.log(JSON.stringify(results, null, 2));
Run it with node capture.mjs. Replace the sample URLs and IDs with your directory data. Use stable IDs that are safe as filenames; do not derive filenames directly from arbitrary URLs. The manifest records the timestamp, HTTP status when available, and error so your publishing workflow can distinguish fresh images from stale ones.
The example waits for DOM content and then a short fixed delay. That is a compromise for a general sample, not a guarantee that every site has finished rendering. For sites you control or a known template, wait for a meaningful selector instead. For varied external sites, validate a sample and adjust the wait policy without allowing a single slow page to block the whole batch indefinitely.
4. Store, resize, and refresh the output
Keep the canonical capture in storage your application controls, then create a derivative sized for the directory card. Avoid serving a huge original where a smaller image will look the same at its displayed size. Google’s image guidance recommends high-quality representative images, avoiding extreme aspect ratios, and using image optimization and responsive image techniques; it also notes that images can contribute substantially to page size.
- Keep a stable association between a listing and its preview so a refresh replaces the right image.
- Store the capture date and consider a scheduled refresh cadence appropriate to how often directory sites change.
- Keep the previous good image until a replacement succeeds. Do not replace it with a blank or failed capture.
- If you use a hosted provider, check whether its output URL expires. OpenGraph.io documents that returned screenshot URLs expire after 24 hours, so download or cache output that must persist. See its screenshot documentation.
- Serve the card derivative responsively and compress it. Check the actual file size and appearance rather than assuming one format will suit every image.
5. Build accessible directory cards
A screenshot should not be the only way to identify or open a listing. Keep the blog name and short description as text, and make the destination available as an ordinary link. Use alternative text that describes the image in context; Google advises useful alt text and warns against keyword stuffing in its image guidance.
<article class="blog-card">
<a class="blog-card__preview" href="https://example.com/">
<img
src="/media/previews/example-travel-blog-640.webp"
width="640"
height="400"
loading="lazy"
alt="Homepage preview of Example Travel Blog"
>
</a>
<h2><a href="https://example.com/">Example Travel Blog</a></h2>
<p>Slow travel notes and practical city guides.</p>
</article>
Set the width and height to the dimensions of the displayed derivative to reserve layout space as it loads. Add a visible fallback when the image is missing or fails to load. For example, the application can render a neutral placeholder with the site name instead of leaving a broken-image icon. Keep alt text concise and specific; if the adjacent linked title already names the blog, use the surrounding structure to avoid repeating the same label needlessly.
6. Choosing a capture route
For a very small directory, manual capture minimizes setup. For recurring batches, choose automation based on control, output persistence, failure handling, and the effort of operating browser workers. Cloudflare Browser Run documents screenshot capture from a URL or HTML, viewport and full-page options, selector capture, and navigation waiting in its screenshot endpoint documentation. OpenGraph.io documents image format and quality controls, viewport presets, full-page capture, selectors, and cache behavior in its screenshot API documentation.
Those docs establish available controls, not comparative pricing, quotas, or measured performance. Check current terms directly before selecting another service. When comparing any route, assess:
- Volume: how many sites need initial capture and how often previews are refreshed.
- Control: viewport, full-page or selector capture, wait behavior, and output format.
- Persistence: where images live, whether returned URLs expire, and how updates replace old files.
- Reliability: how the workflow handles slow sites, access blocks, consent overlays, and blank output.
- Operations and cost: browser runtime or API charges, storage, retries, and the time needed to monitor failed captures.
- Reader experience: derivative size, mobile layout, useful alt text, and a visible text fallback.
7. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. Its one-request API can return an image or PDF. The basic cURL, Python, and Node.js requests below capture a public homepage; see the ScreenshotNeo API documentation for request options and configuration.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" \
-d access_key=YOUR_API_KEY \
--data-urlencode url=https://example.com/ \
-o example-travel-blog.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={
"access_key": "YOUR_API_KEY",
"url": "https://example.com/",
},
timeout=90,
)
r.raise_for_status()
with open("example-travel-blog.webp", "wb") as image_file:
image_file.write(r.content)
Node.js
const q = new URLSearchParams({
access_key: process.env.SCREENSHOTNEO_API_KEY,
url: 'https://example.com/',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) {
throw new Error(`Screenshot request failed: ${res.status} ${res.statusText}`);
}
const image = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(({ writeFile }) =>
writeFile('example-travel-blog.webp', image)
);
These examples save the response locally. For a directory, upload successful results to your media storage, generate a card-sized derivative, and preserve the prior image if a capture fails. ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture; each of those steps can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Sign up for free and make your first captures.
8. Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| Preview is blank or mostly empty | The site renders after the navigation event, requires JavaScript, or returned an access challenge. | Use a bounded wait for a meaningful element or a modest delay, then inspect the result. If the site blocks automation, keep the fallback and consider asking the site owner for a share image. |
| Some pages time out and stop the batch | A navigation or resource never settles, or the wait policy is too strict. | Use a per-page timeout, catch errors per URL, and continue with the rest of the batch. Record failures for later review. |
| Card crops cut off the site name or hero image | The homepage layout differs, or the crop is too tight for the chosen ratio. | Review the captured derivative and adjust the crop or use a viewport capture that better matches the card. Keep the card title as visible text regardless. |
| Images are inconsistent in size or card layout jumps | Originals have different dimensions or the page does not reserve image space. | Generate uniform derivatives and include image dimensions in markup or CSS aspect ratio. |
| Stored preview link stops working | The provider may expire its hosted output URL. | Check the provider’s documented retention behavior and copy the file to storage you control when persistence is needed. |
| Cookie notice or chat widget obscures the page | The page overlays content before capture. | Decide whether the overlay is part of the preview. For a do-it-yourself browser workflow, handle only known, permitted page interactions; do not assume every site uses the same selector. ScreenshotNeo removes known consent platforms, newsletter popups, and chat widgets before capture. |
| API request returns an error instead of an image | The request may be unauthorized, malformed, or the target could not be captured. | Check the API key, encoded URL, HTTP status, and response details. Do not save an error response as an image; preserve the previous successful preview. |
9. Performance, reliability, and cost
Capturing and serving previews are separate workloads. Browser rendering and remote site response time determine how long a capture job takes; image dimensions and compression determine how much data directory visitors download. Batch captures at a controlled rate, set timeouts, isolate failures per URL, and avoid refreshing every image on every directory page request. Cache the generated derivative and refresh it through a background workflow.
For reliability, keep the last successful capture, record when it was made, and display a fallback when none exists. Some websites may block automated requests, require interaction, load slowly, or render differently at desktop dimensions. Test a varied sample before processing the full list. Cloudflare describes its endpoint as rendering HTML and JavaScript before capturing the page in its Browser Run documentation; that does not imply every external site will be capturable under every condition.
For cost, include browser execution or API usage, retries, image storage, and refresh frequency in the estimate. This research does not establish comparative prices or performance for other providers, so verify their current limits and terms directly. ScreenshotNeo’s listed plans are Free for 1,000 shots per month, Starter $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000; yearly billing gives two months free, and every feature is on every plan. Only clean shots are billed under its stated policy.
10. Launch checklist
- Choose screenshot or social image and label the directory consistently.
- Use one capture viewport and one card ratio for the full list.
- Save durable copies and derivatives at display size.
- Record capture dates and retain the previous good image on failure.
- Show the blog name and description as text, with meaningful alt text and a visible destination.
- Test broken-image fallback, mobile layout, slow pages, and blocked captures.
- Check current provider quotas, output retention, and pricing before committing.
FAQ
Should every directory preview be clickable?
It can be, but provide a normal text link to the blog as well. That keeps the destination clear if the image is unavailable or not recognized by a visitor.
Should I show the capture date?
Store it even if it is not prominent in the card. If freshness matters to your directory, display a concise date or freshness label and define what it means.
Can I use a full-page screenshot as the card image?
Yes, but its tall shape is often hard to read at card size. Use it when readers need a longer record and provide a separate crop or preview for the grid.


