How to keep website thumbnails consistent across a curated link list
Give every link card a consistent image box, choose a crop policy that preserves meaning, and handle missing or unsuitable thumbnails gracefully.
To keep thumbnails consistent across a curated link list, give every card the same image container shape, then choose deliberately between filling that box with a crop and fitting the whole image inside it. Use representative images, set a focal point when a crop could hide important content, and provide a neutral fallback for missing or unsuitable images. The source image and the way your list displays it are separate decisions: a good representative image may still have the wrong proportions for your card.
1. Choose a shared thumbnail shape
There is no universal thumbnail size required for a curated link list. Pick a shape that fits the design, then apply it consistently across the list. An aspect ratio is usually more adaptable than a fixed pixel height: the browser can maintain the shape as the card width changes.
A 16:9 box works well for many editorial and video-like lists; a square box can suit icon-heavy collections; a taller box can suit covers or portrait-oriented content. These are design choices, not platform requirements. Test the layout with wide, square, and portrait source images before settling on one.
<ul class="link-list">
<li class="link-card">
<a href="https://example.com/article">
<span class="link-card__media">
<img src="https://example.com/preview.jpg"
alt="A representative image for the linked article"
loading="lazy">
</span>
<span class="link-card__title">Example article</span>
</a>
</li>
</ul>
.link-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
gap: 1.25rem;
margin: 0;
padding: 0;
list-style: none;
}
.link-card__media {
display: block;
aspect-ratio: 16 / 9;
overflow: hidden;
background: #f0f1f2;
border-radius: 0.5rem;
}
.link-card__media img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
object-position: center;
}
.link-card__title {
display: block;
margin-top: 0.65rem;
}
The media wrapper owns the shape. The image fills the wrapper, so inconsistent intrinsic image dimensions do not change card geometry. overflow: hidden clips the parts outside the frame.
2. Pick a crop policy that matches the content
With a shared box in place, choose how each source image fits inside it. CSS object-fit makes the tradeoff explicit.
| Policy | CSS | Result | Use when |
|---|---|---|---|
| Fill and crop | object-fit: cover |
Fills the whole box; parts outside the box are cropped. | A uniform grid matters and the subject remains clear when cropped. |
| Show the whole image | object-fit: contain |
Preserves the entire source image; empty space may remain. | Logos, diagrams, screenshots, or text inside the image must remain visible. |
| Stretch | object-fit: fill |
Distorts the image to the box shape. | Usually avoid; distortion can change the apparent shape of the subject. |
For a filled grid, cover is the common choice. But automatic center cropping can remove a face, product, chart label, or other meaningful detail. Set object-position per image when the subject is off-center:
<span class="link-card__media">
<img src="https://example.com/portrait.jpg"
alt="A portrait of the article's author"
style="object-position: 50% 24%"
loading="lazy">
</span>
.link-card__media img {
object-fit: cover;
object-position: 50% 24%; /* x position, y position */
}
For many items, store focal-point values with the curated link data instead of hard-coding inline styles. For example, a record can hold imageUrl, alt, and a normalized focal point such as 50% 24%. Use a default of 50% 50% when no editorial adjustment is needed.
3. Select representative images and control metadata
Prefer an image that actually represents the destination page. Google’s image guidance recommends relevant, representative images, and cautions against generic logos and extreme aspect ratios. It also recommends high-resolution images where possible. Those recommendations concern image discovery and search; your own card component still needs its own layout rules. Google image SEO best practices
If you control the destination pages, set explicit Open Graph metadata in the initial HTML response. The protocol defines og:image as the image representing the page and supports structured image properties for dimensions, MIME type, secure URL, and alt text. Its guidance says that a page specifying og:image should also specify og:image:alt. Open Graph protocol
<head>
<meta property="og:title" content="A useful guide to the topic">
<meta property="og:type" content="article">
<meta property="og:url" content="https://example.com/guide">
<meta property="og:image" content="https://example.com/images/guide-preview.jpg">
<meta property="og:image:secure_url" content="https://example.com/images/guide-preview.jpg">
<meta property="og:image:type" content="image/jpeg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="A diagram explaining the guide's main idea">
</head>
For a curated list that reads those tags from other sites, treat metadata as input, not a guarantee. A page may omit an image, expose a stale URL, return an image the list cannot load, or use an image whose proportions do not suit your card. Validate and normalize the result before rendering.
External previews have their own rules
Your CSS controls thumbnails inside your own page. It does not control how a messaging app or social platform renders a preview of one of your links. Apple’s Messages guidance says previews are built from metadata available directly on the linked page, and Messages does not run that page’s JavaScript for preview generation. So if you own the page, server-render the metadata rather than relying on client-side updates. Apple’s stated image-size guidance for Messages is specific to Messages previews; it is not a required size for the thumbnails in your own list. Apple: Create rich previews for Messages
4. Handle missing, broken, and unsuitable images
Choose a fallback before you publish the list. A neutral color or designed placeholder keeps card dimensions steady without pretending to show page content. If there is a reliable alternative image you control, use that. Do not let a missing image collapse the media region or produce a broken-image icon.
<span class="link-card__media">
<img
src="https://example.com/preview.jpg"
alt="A representative image for the linked article"
loading="lazy"
onerror="this.hidden = true; this.parentElement.classList.add('is-empty')">
</span>
.link-card__media.is-empty {
background: #f0f1f2;
}
.link-card__media img[hidden] {
display: none;
}
For a production component, prefer an image-load handler in your framework or client code over inline event attributes, especially where your content security policy disallows inline handlers. The handler should mark the wrapper empty and preserve its aspect ratio. If you use a placeholder image, ensure it is decorative when the link title already names the destination: an empty alt may be appropriate in that case. If the image communicates information not present in the title, provide a concise useful description.
- Reject or replace non-HTTP image URLs if your list only supports web-hosted images.
- Handle redirects and expired or access-controlled image URLs as load failures.
- Do not trust remote dimensions blindly; the response can be missing dimension metadata or the file may not match its declared type.
- Keep the same wrapper size whether the image loads or falls back.
- For content moderation or source policy, define what makes an image unsuitable and use the fallback consistently.
5. Build a complete thumbnail card
This standalone example uses ordinary HTML and CSS. It keeps cards aligned, supports an editorial focal point, and retains the image frame when an image fails. Change object-fit to contain if preserving the whole source matters more than filling the frame.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Curated links</title>
<style>
* { box-sizing: border-box; }
body { margin: 2rem; font: 1rem/1.5 system-ui, sans-serif; color: #202124; }
.link-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
gap: 1.25rem;
margin: 0;
padding: 0;
list-style: none;
}
.link-card a { color: inherit; text-decoration: none; }
.link-card__media {
display: block;
aspect-ratio: 16 / 9;
overflow: hidden;
border-radius: .5rem;
background: #eceff1;
}
.link-card__media img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
object-position: var(--focal-point, 50% 50%);
}
.link-card__media.is-empty::after {
content: "";
display: block;
width: 100%;
height: 100%;
background: linear-gradient(135deg, #eceff1, #dfe4e8);
}
.link-card__media img[hidden] { display: none; }
.link-card__title { display: block; margin-top: .65rem; font-weight: 650; }
.link-card__host { display: block; margin-top: .15rem; color: #5f6368; font-size: .875rem; }
</style>
</head>
<body>
<ul class="link-list">
<li class="link-card">
<a href="https://example.com/article">
<span class="link-card__media" style="--focal-point: 50% 28%">
<img src="https://example.com/wide-preview.jpg"
alt="A mountain landscape from the article"
loading="lazy"
onerror="this.hidden=true;this.parentElement.classList.add('is-empty')">
</span>
<span class="link-card__title">A guide to the landscape</span>
<span class="link-card__host">example.com</span>
</a>
</li>
<li class="link-card">
<a href="https://example.org/story">
<span class="link-card__media">
<img src="https://example.org/square-cover.jpg"
alt="A square illustration for the story"
loading="lazy"
onerror="this.hidden=true;this.parentElement.classList.add('is-empty')">
</span>
<span class="link-card__title">A story with a square cover</span>
<span class="link-card__host">example.org</span>
</a>
</li>
</ul>
</body>
</html>
The example uses inline error handlers to remain a single copyable file. In a deployed application, move the same behavior into your component’s event handling if your content security policy blocks inline JavaScript. Also consider whether cards need keyboard focus styling, an external-link indicator, and a descriptive accessible name; those choices depend on the surrounding design.
6. If you fetch images or metadata on your server
A curated list often has a save-URL workflow: a user submits a link, the application reads page metadata, and the UI displays the selected image. Keep that workflow separate from card styling. The CSS handles dimensions and crop; your fetch pipeline selects, validates, and stores a usable image URL.
- Accept and normalize the page URL. Allow only schemes your product intends to fetch, generally HTTP and HTTPS.
- Fetch the page HTML with a bounded timeout and response-size limit. Handle redirects deliberately.
- Read
og:imagefrom the HTML head and resolve relative image URLs against the final page URL. If the page has no usable image, apply your defined fallback. - Validate the resulting URL and image response. Check status, content type, and reasonable file-size limits rather than assuming the metadata is accurate.
- Store the source URL and any editorial focal point with the link record. If you copy or transform an image, make sure your product has the necessary rights and follows the source’s terms.
- Render all records through the same aspect-ratio wrapper and crop policy.
Fetching arbitrary user-supplied URLs from a backend introduces security concerns as well as reliability concerns. Apply your application’s SSRF protections: block loopback, private, link-local, and other internal destinations; re-check redirect targets; and restrict outbound access where possible. Do not pass user-controlled URLs into a privileged network request without those controls.
If you own the destination page, server-render its Open Graph tags. The Open Graph protocol defines metadata in the document head, and platform preview generators may not execute client-side code. A screenshot can help inspect what a page visually renders, but it does not replace reading its metadata when your goal is to extract og:image.
7. Test the edge cases that cause uneven lists
- Wide landscape: verify that the subject remains visible after cropping to the chosen ratio.
- Square image: confirm it fills or fits the box according to your policy.
- Tall portrait: adjust the focal point if
covercuts off the subject. - Diagram or screenshot: use
containwhen cropping would remove labels or useful context. - Transparent image: give the media box a background color so transparent pixels do not blend unpredictably into the page.
- Missing or broken image: confirm the placeholder keeps the same geometry.
- Very large source: avoid downloading a huge original for a small card if you control an image resizing pipeline.
- Changed image URL: refresh stored metadata or retain a fallback so a previously valid card does not become permanently empty.
- Responsive layout: check the component at narrow and wide card widths; the ratio should stay stable as the grid changes columns.
8. Performance, reliability, and cost
For a browser-rendered list, reserve the thumbnail’s space with aspect-ratio to avoid layout shifts while images load. Use lazy loading for below-the-fold cards, and avoid loading oversized source images when smaller versions are available. Google notes that images can be a major contributor to page weight and recommends image optimization and responsive image techniques. Google image guidance
If the application fetches metadata at link-save time, place timeouts and size limits on page and image requests. Cache the extracted metadata for a chosen period so revisiting the same URL does not trigger repeated remote fetches. Build a refresh path because destination pages can change or remove their image. Serve your own resized derivative only if you have permission and an appropriate storage and invalidation policy.
The cost of this design depends on your hosting, storage, bandwidth, and any image processing or metadata service you choose; the dossier provides no universal price or benchmark. Measure real image payload sizes and request volume for your own list. A small thumbnail with a large original URL can cost more in transfer than its display size suggests.
9. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Card heights differ | The image itself controls the layout, or the wrapper has no fixed ratio. | Give every media wrapper the same aspect-ratio; size the image to 100% width and height. |
| Important content is cut off | object-fit: cover is cropping around the center. |
Set a per-image object-position, select another representative source, or use contain. |
| Blank bands appear beside an image | contain preserves the full image but does not fill the box. |
Accept the letterboxing, use a background that suits the image, or switch to cover when cropping is safe. |
| Image icon or empty area appears | Remote URL is invalid, blocked, expired, or returns an error. | Handle load errors, preserve the frame, and display the chosen neutral fallback. |
| Preview app shows the wrong image | The consumer reads destination metadata rather than your list’s CSS; metadata may be absent or cached. | Set correct metadata on pages you control and follow the consumer’s preview refresh process. Your CSS cannot control third-party preview rendering. |
| Remote metadata fetch fails in browser code | The destination does not permit cross-origin reads, or the request is blocked. | Fetch server-side with SSRF protections, or use metadata already supplied by a trusted source. |
| Images load slowly or shift content | Original images are large, space is not reserved, or too many images load immediately. | Set an aspect ratio, lazy-load below-the-fold images, and use suitable responsive image sizes. |
| Inline error handler does nothing | Content Security Policy blocks inline JavaScript. | Move the error handling to an external script or framework event handler. |
10. Or skip the browser setup
If you need a visual record of how a destination page renders, ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. It returns a PNG, JPEG, WebP, or PDF from one GET request. A screenshot can complement metadata extraction when you want a visual snapshot; it is not a substitute for the page’s og:image if the list needs that exact source image.
See the ScreenshotNeo API documentation. This cURL request saves a WebP screenshot of a page:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.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,
)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({
access_key: 'YOUR_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}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));
ScreenshotNeo accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers report the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots. Every feature is available on every plan.
Create a free ScreenshotNeo account and get 1,000 screenshots a month with no card.
FAQ
Should every thumbnail use the same pixel dimensions?
Use the same display ratio for consistent card geometry. The rendered pixel dimensions can change with responsive layout and device density.
Does setting og:image make every app show that image?
No. Open Graph defines metadata that consumers can read, but the consuming platform controls its preview behavior. Your curated page’s CSS only controls your own rendering.
Should I store an image URL or copy the image?
Storing the source URL avoids managing another image copy but depends on the remote host continuing to serve it. Copying or transforming an image adds storage, refresh, rights, and invalidation responsibilities.
Can I use a screenshot as a thumbnail?
Yes, if a rendered snapshot is the visual you want and you have rights to use it. For the destination’s designated preview image, read its metadata instead.


