How to Generate Social Media Cards and Link Previews
Generate page-specific Open Graph tags, publish a crawler-accessible image, and validate the live link preview across the platforms you use.
A social media link preview is generated from metadata in the page’s HTML, plus an image that the platform’s crawler can fetch. For each page, render accurate Open Graph tags in the initial HTML response, use an absolute public image URL, publish the page, and inspect the live URL with the target platform’s preview tool.
This guide shows the metadata to add, a runnable Node.js example that generates it per page, image requirements to check, and a validation and troubleshooting workflow. Platform previews can differ, so verify the result on each service where people will share your links.
1. Choose the preview data for each page
For every page whose content differs, choose its own title, description, canonical URL, and social image. A site-wide default can fill gaps, but it should still describe the page accurately. Reusing the homepage’s metadata for every route can produce a card that is technically valid but misleading.
- Title: a concise, accurate name for the specific page.
- Description: a short summary that helps someone decide whether to open it.
- URL: the canonical absolute URL for the page, using HTTPS where available.
- Image: an absolute HTTPS URL to a suitable image that the platform crawler can retrieve without signing in.
- Image alternative text: describe what the image communicates. This is not a caption; Open Graph recommends providing it when
og:imageis present.
The Open Graph protocol identifies og:title, og:type, og:image, and og:url as required properties. It also defines og:description and image details such as width, height, MIME type, secure URL, and alternative text. See the Open Graph protocol reference and web.dev’s social discovery guide.
2. Add Open Graph tags to the HTML head
Here is a practical baseline for an article or landing page. Replace the example values with the page’s real data. The image address must be absolute so a crawler can resolve it independently of the page URL.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>A specific, accurate page title</title>
<meta name="description" content="A short summary of this page.">
<link rel="canonical" href="https://example.com/guides/social-cards">
<meta property="og:type" content="website">
<meta property="og:title" content="A specific, accurate page title">
<meta property="og:description" content="A short summary of this page.">
<meta property="og:url" content="https://example.com/guides/social-cards">
<meta property="og:image" content="https://example.com/images/social-cards.jpg">
<meta property="og:image:alt" content="A page preview card with an illustration of a shared link.">
<meta property="og:image:type" content="image/jpeg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
</head>
<body>
<h1>A specific, accurate page title</h1>
</body>
</html>
Set og:type to an appropriate type for the page. The protocol’s basic example uses website; it defines other object types as well. Keep the HTML title, description, canonical link, and Open Graph values consistent, while using each field for its intended purpose.
Optional X card metadata
If you want a large preview on X, a platform-specific card declaration may be useful. This example follows the fields shown in the Great Docs implementation guide. Check the target platform’s current documentation before relying on platform-specific behavior; one set of tags does not guarantee identical rendering everywhere.
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="A specific, accurate page title">
<meta name="twitter:description" content="A short summary of this page.">
<meta name="twitter:image" content="https://example.com/images/social-cards.jpg">
For other services, begin with Open Graph, then add service-specific fields only where their current guidance calls for them. LinkedIn’s sharing guidance lists title, image, description, and URL tags for its sharing module. See the LinkedIn Help guidance and Great Docs’ social card implementation example.
3. Generate metadata for every route
In a static site, generate the tags at build time. In a server-rendered application, generate them while rendering the response. In either case, check the built or served HTML for each route: the metadata should be present in the initial document’s <head>, with values that match that page. Great Docs demonstrates per-page tag generation and checking the built HTML; its example does not establish that every client-rendered implementation will be crawled consistently.
Here is a complete Node.js example using only the built-in HTTP module. It serves two routes with different metadata in their initial HTML. Save it as server.mjs and run node server.mjs. In a real application, replace the in-memory page records and sample image addresses with your content and publicly accessible assets.
import http from 'node:http';
const pages = {
'/guides/social-cards': {
title: 'Generate Social Cards for Your Pages',
description: 'Add page-specific Open Graph metadata and validate the preview.',
image: 'https://example.com/images/social-cards.jpg',
alt: 'Illustration of a shared webpage becoming a social preview card.'
},
'/guides/image-access': {
title: 'Make Preview Images Accessible to Crawlers',
description: 'Check public image URLs and common reasons previews omit an image.',
image: 'https://example.com/images/crawler-access.jpg',
alt: 'An image served from a public website address.'
}
};
function escapeHtml(value) {
return String(value).replace(/[<>&"']/g, (char) => ({
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
}[char]));
}
const server = http.createServer((req, res) => {
const pathname = new URL(req.url, 'http://localhost').pathname;
const page = pages[pathname];
if (!page) {
res.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' });
res.end('Not found');
return;
}
const canonical = `https://example.com${pathname}`;
const title = escapeHtml(page.title);
const description = escapeHtml(page.description);
const image = escapeHtml(page.image);
const alt = escapeHtml(page.alt);
const html = `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>${title}</title>
<meta name="description" content="${description}">
<link rel="canonical" href="${escapeHtml(canonical)}">
<meta property="og:type" content="website">
<meta property="og:title" content="${title}">
<meta property="og:description" content="${description}">
<meta property="og:url" content="${escapeHtml(canonical)}">
<meta property="og:image" content="${image}">
<meta property="og:image:alt" content="${alt}">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="${title}">
<meta name="twitter:description" content="${description}">
<meta name="twitter:image" content="${image}">
</head>
<body><h1>${title}</h1><p>${description}</p></body>
</html>`;
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
res.end(html);
});
server.listen(3000, () => {
console.log('Open http://localhost:3000/guides/social-cards');
});
The example escapes values before inserting them into HTML attributes, returns a clear 404 for unknown routes, and derives the canonical URL from the route. In production, use your framework’s HTML escaping and routing mechanisms, and make sure the canonical host matches the public site. Do not blindly trust metadata values from user input.
4. Prepare an image that fits the target platform
Choose an image that remains understandable at small preview size. Use a page-specific image when the content calls for one, and keep important visual details within the area likely to remain visible after a crop. Confirm that the file’s actual format matches its URL and declared MIME type.
LinkedIn Help states a minimum image size of 1200 × 627 pixels, a maximum file size of 5 MB, and a recommended 1.91:1 ratio for organic sharing. LinkedIn also says images under 401 pixels wide display as thumbnails. These are LinkedIn-specific figures, not universal limits; the help page was last updated two years before the 2026 research access date, so recheck its live guidance before publishing exact requirements. Great Docs recommends 1200 × 630 pixels for its workflow, with PNG for graphics or text and JPEG for photos; that is its recommendation, not a cross-platform standard.
| Check | What to verify |
|---|---|
| Dimensions | Meet the requirements of each target service; do not assume a LinkedIn size rule applies elsewhere. |
| File size | Check the destination platform’s current maximum and keep the image below it. |
| Accessibility | Use a public, absolute image URL the crawler can fetch without authentication. |
| Composition | Keep the main subject clear at a small size and allow for platform cropping. |
| Alt text | Add og:image:alt that describes what the image communicates. |
5. Publish and validate the live preview
- Deploy the page and image. Ensure the final public route returns the intended page and its image URL resolves without authentication or crawler-blocking rules.
- Inspect the raw HTML. View the deployed page source or fetch its HTML. Confirm the expected Open Graph tags appear in the
<head>, values are specific to this route, and the image URL is absolute. - Check the image itself. Open the exact image URL and confirm it returns the expected asset. Check that redirects, access controls, and server rules do not prevent crawler retrieval.
- Use the target platform’s preview or inspection tool. Confirm the displayed title, description, image, crop, and destination URL. Great Docs lists LinkedIn Post Inspector and Facebook Sharing Debugger as examples; check that the relevant tool remains available before relying on it.
- Repeat for important routes and platforms. A correct homepage card does not prove that every page has distinct metadata or that every service renders it the same way.
After changing tags, inspect the live HTML first. If it is correct but a service still shows the old card, use that service’s available inspector or cache-refresh workflow. Cache duration is platform-specific; the sources reviewed do not establish a universal refresh time.
6. Troubleshooting missing or incorrect previews
| Symptom | Likely cause | What to fix |
|---|---|---|
| No image appears | The image URL is relative, inaccessible, blocked, protected, or outside the platform’s supported constraints. | Use an absolute URL, check it publicly, remove access restrictions that block the crawler, and verify the destination’s format, dimensions, and file-size rules. |
| Old title or image appears | The service may be using previously fetched preview data, or the live HTML still contains old values. | Inspect the deployed HTML. If correct, run the platform’s available inspector or cache-refresh workflow and check the preview again. |
| Every route shows the same card | A shared template may be applying site-wide defaults to every page. | Generate title, description, canonical URL, and image from the current route’s page data. Check multiple deployed URLs. |
| Browser page looks right, preview does not | Metadata may be added only after client-side JavaScript runs, or the crawler may not receive the same response. | Render metadata in the initial HTML at build time or on the server, then inspect the raw deployed response. |
| Wrong image or unexpected crop | The tag points to another asset, the image is unsuitable for the target ratio, or the platform crops it differently. | Verify the exact image URL and preview crop with the target platform’s inspector; adjust the composition and retest. |
| Preview fields disagree | Open Graph, platform-specific tags, HTML title, and canonical URL may not describe the same page. | Review all fields together and make each one consistent with the page and its intended public URL. |
7. Performance, reliability, and maintenance
Social metadata is small, but generating it at the right time matters. Build-time generation suits static pages; server rendering suits pages whose data changes dynamically. Both approaches should produce complete tags in the HTML response served for each URL. Avoid making correctness depend solely on a later client-side render.
Use an image host that serves the asset reliably to unauthenticated requests and does not require an interactive session. Keep a default image available for pages that lack a custom one, but make the default a truthful representation. When metadata changes, inspect the deployed output and refresh the preview through the destination service’s available process. Recheck platform rules when they matter to a launch, since image limits and preview behavior vary and can change.
8. Capture a rendered page for visual review
Metadata tells a platform what preview information to use. A screenshot can help a developer review how the destination page itself renders, but it does not replace checking the page’s HTML metadata or the platform’s actual preview. For repeatable visual checks, use a browser automation tool or screenshot API with the page URL, then compare the rendered result after a deployment.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request captures a page as PNG, JPEG, WebP, or PDF. Its clean-shot steps can accept cookie and consent banners like a visitor and remove more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks or CAPTCHAs, 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 AI agents.
For this page, the one-call request is:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/guides/social-cards -o shot.webp
See the ScreenshotNeo API documentation for request options. Free includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots. Sign up for 1,000 free screenshots a month, with no card.
FAQ
Do I need Open Graph tags if the page already has a meta description?
A normal HTML description and Open Graph description are separate fields. Add the Open Graph fields for the share metadata, and keep the page’s ordinary title and description accurate too.
Can one image work for every platform?
It can, but rendering and image rules vary by platform. Check the card on each service you care about and use platform-specific metadata or image variants when needed.
Does an image’s alt text replace its description?
No. og:image:alt describes the image; og:description summarizes the page.
Will changing tags immediately update an existing share?
Not necessarily. Check the live HTML, then use the platform’s available inspection or refresh process. There is no universal cache duration established by the sources reviewed.


