How to Generate a Website Meta Image for Social Sharing
Create a social sharing image, add the right Open Graph tags, and fix previews that fail to appear.
Create a 1200 × 630 pixel image, host it at a publicly reachable HTTPS URL, and reference it in your page’s <head> with og:image. Add the page’s title, description, canonical URL, and image alt text as Open Graph metadata. The same image can also serve as a large Twitter card image. Then check the page source and image URL, and refresh the destination platform’s preview if it has cached an older version.
This guide shows a manual workflow, a runnable Python image generator, the metadata to add, framework options, and fixes for common preview problems.
1. Choose the image size and design
Use 1200 × 630 pixels as a practical default. Its aspect ratio is about 1.91:1, close to LinkedIn’s documented recommendation. LinkedIn specifies a minimum of 1200 × 627 pixels and a 5 MB maximum. Other platforms may have different display behavior, so keep essential content away from the edges and check the result on the platforms where your audience shares links.
- Use a clear focal point and strong contrast.
- Keep the headline brief and legible when the image is displayed small.
- Allow room around the edges for cropping. Avoid placing important details at the far left, right, top, or bottom.
- Export a PNG or JPEG. WebP can be useful where the target platform supports it, but PNG or JPEG is a safer choice when compatibility is uncertain.
- Compress the file and check its dimensions and file size before publishing.
The Open Graph protocol defines the image URL and optional image properties such as type, width, height, secure URL, and alt text. See the Open Graph protocol specification and LinkedIn’s sharing requirements.
2. Generate an image with Python
This example creates a 1200 × 630 PNG using Pillow. Install the dependency, save the script as make_og_image.py, and run it. The design uses a solid background and centered title; replace those with your brand colors, typography, or page-specific artwork as needed.
python -m pip install Pillow
from PIL import Image, ImageDraw, ImageFont
from pathlib import Path
import textwrap
WIDTH, HEIGHT = 1200, 630
BACKGROUND = "#101827"
FOREGROUND = "#ffffff"
ACCENT = "#76e4c1"
TITLE = "A useful page title goes here"
OUTPUT = Path("og-page.png")
image = Image.new("RGB", (WIDTH, HEIGHT), BACKGROUND)
draw = ImageDraw.Draw(image)
# Use a system font when available; Pillow's default font is the fallback.
font_candidates = [
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
"/Library/Fonts/Arial Bold.ttf",
"C:/Windows/Fonts/arialbd.ttf",
]
font_path = next((p for p in font_candidates if Path(p).exists()), None)
font = ImageFont.truetype(font_path, 68) if font_path else ImageFont.load_default()
# A simple accent bar gives the card a visual anchor.
draw.rounded_rectangle((72, 72, 230, 86), radius=7, fill=ACCENT)
# Wrap long titles so they remain inside the image's safe area.
max_width = 1000
words = TITLE.split()
lines = []
line = ""
for word in words:
candidate = f"{line} {word}".strip()
if draw.textbbox((0, 0), candidate, font=font)[2] <= max_width:
line = candidate
else:
if line:
lines.append(line)
line = word
if line:
lines.append(line)
line_height = 88
block_height = len(lines) * line_height
start_y = (HEIGHT - block_height) // 2
for index, line in enumerate(lines):
draw.text((72, start_y + index * line_height), line, font=font, fill=FOREGROUND)
image.save(OUTPUT, format="PNG", optimize=True)
print(f"Wrote {OUTPUT} ({WIDTH}x{HEIGHT})")
For production, choose a font available in your build environment rather than relying on a machine-specific path. If you generate images for many pages, test long titles, non-Latin characters, and missing or unusually large font files. Keep the final output within the size limit of the platforms you target.
3. Host the image and add Open Graph metadata
Put the image at an absolute, publicly fetchable HTTPS URL, for example https://example.com/images/og-page.png. Add a consistent set of metadata to the document head. Replace the example values with the current page’s actual title, description, canonical URL, and image URL.
<head>
<title>Page title</title>
<link rel="canonical" href="https://example.com/page">
<meta property="og:title" content="Page title">
<meta property="og:description" content="Short description for the shared link">
<meta property="og:type" content="website">
<meta property="og:url" content="https://example.com/page">
<meta property="og:image" content="https://example.com/images/og-page.png">
<meta property="og:image:alt" content="A brief description of the image">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:type" content="image/png">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="https://example.com/images/og-page.png">
</head>
og:title, og:type, og:url, and og:image are the basic Open Graph properties. A useful description and og:image:alt help describe the shared page and image. The Open Graph specification also defines structured image properties. LinkedIn’s documented markup uses og:title, og:image, og:description, and og:url. Consult the specification for property details and LinkedIn’s requirements for its platform-specific constraints.
Metadata should be present in the HTML delivered to crawlers. If your framework renders the page on the client, verify the response HTML itself rather than relying only on what appears after JavaScript runs. Keep the Open Graph URL, canonical URL, and page content aligned; inconsistent values can result in a preview for a different URL or page than you intended.
4. Generate images per route in Next.js
For a site with many pages, a route-specific image avoids maintaining a separate static file for every page. Next.js supports opengraph-image and twitter-image file or route conventions in the relevant route segment. Its documented limits are 8 MB for opengraph-image and 5 MB for twitter-image. Follow the current Next.js Open Graph image documentation for the supported formats and route behavior.
Choose between a static image and generated output based on how often the card changes and whether each route needs its own title or data:
| Approach | Best fit | Trade-off |
|---|---|---|
| One static image | A small site or pages that share one visual | Simple to deploy, but every page may show the same artwork |
| Static image per route | Pages with distinct artwork that changes rarely | Easy to inspect, but assets must be maintained as pages change |
| Generated image per route | Many pages whose title or content should appear in the card | Personalized output, with build or runtime generation to maintain and debug |
Regardless of how an image is generated, the published result still needs a fetchable image URL, valid metadata, and dimensions and file size suitable for the destination platform.
5. Verify the page and preview
- Open the page’s raw HTML or view-source output. Confirm the Open Graph tags are in the head and contain the expected values.
- Open the
og:imageURL directly. Confirm it returns the image rather than an HTML page, an authentication screen, or an error. - Check the image’s dimensions, format, and file size. For LinkedIn, the documented minimum is 1200 × 627 pixels and the maximum is 5 MB.
- Use the destination platform’s preview or re-fetch workflow after changing the page or image. A platform may retain an older preview, and cache behavior differs across platforms.
- Share a representative page URL and inspect the resulting card, including title, description, crop, and image alt text where exposed.
Open Graph metadata describes a page as a rich object in a social graph, as explained by the Open Graph protocol. Platform preview tools help confirm what a particular crawler currently sees; they do not replace checking the page HTML and image URL directly.
Or skip the browser setup
If the image design is a web page or route, you can capture that page as an image with ScreenshotNeo, a website screenshot API and MCP server. Add the API call to a script after publishing the route. The resulting screenshot can be hosted at your own public image URL and referenced by og:image. See the ScreenshotNeo API documentation for request options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/social-card -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.com/social-card"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com/social-card' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(({ writeFile }) => writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot; bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents use screenshot tools. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month, with no card.
Troubleshooting
| Symptom | Likely cause | What to check or change |
|---|---|---|
| No image appears | The image URL is relative, private, blocked, or not an image response | Use an absolute HTTPS URL and open it without signing in. Check that the response serves the intended image. |
| The preview shows an old image | The platform retained a cached preview | Use that platform’s preview debugger or re-fetch workflow. Do not assume a universal cache duration. |
| The wrong page title or URL appears | Open Graph metadata is missing, duplicated, inconsistent, or attached to the wrong route | Inspect the delivered HTML for one correct set of tags, and align og:url with the canonical page URL. |
| The image is cropped unexpectedly | The image ratio or composition does not suit the platform’s display | Use a 1200 × 630 layout as a practical default, keep key content inside a central safe area, and inspect the platform preview. |
| The platform rejects or omits the image | The file may exceed a size limit, have unsupported encoding, or use dimensions outside platform expectations | Check format, dimensions, and file size. For LinkedIn, its documentation specifies 1200 × 627 minimum dimensions and 5 MB maximum. |
| Tags appear in the browser but not to the crawler | Tags may be inserted only after client-side JavaScript runs | Inspect the raw response HTML. Render metadata on the server or provide it in the HTML returned for the route. |
| Image URL returns an error to crawlers | Access controls, hotlink protection, redirects, or firewall rules block retrieval | Make the image publicly fetchable, check redirects and server rules, and verify the exact URL used in og:image. |
| Generated image has clipped text | Long titles, font metrics, or an unavailable font changed the layout | Wrap text, measure rendered text, use a known font in the build environment, and test unusually long titles. |
Performance, reliability, and cost
- Prefer static files when the card rarely changes. They avoid runtime image generation and are straightforward to inspect. Generate images during the build if route data changes with releases.
- Use runtime generation only when it provides value. Per-request generation adds code and operational paths that can fail. Cache generated results where appropriate and ensure the image URL remains stable enough for crawlers.
- Keep files small. Large images take longer to fetch and can exceed platform limits. Compress the output while keeping text and edges clear.
- Make crawler access reliable. Social crawlers need to reach both the page metadata and the image. Avoid authentication requirements and check redirects, firewall rules, and response content.
- Account for platform caching. A correct deployment may not update an already cached preview immediately. Use the destination’s re-fetch mechanism after a change.
- For API-generated screenshots, count the surrounding work. You still need a route that renders the card, an API key, storage or hosting for the resulting image, and metadata pointing to it. ScreenshotNeo’s stated plans range from 1,000 monthly shots free to paid plans starting at $5 for 3,000; only clean shots are billed, and its responses identify page verdict and billing status. See the documentation for options and response details.
Frequently asked questions
Is an Open Graph image the same as a favicon?
No. A favicon identifies a site or page in browser interfaces. An Open Graph image is the artwork used in a shared-link preview.
Can every page use the same image?
Yes. A shared image is suitable when pages do not need distinct preview artwork. Route-specific images are more useful when the page title or subject should be visible in the card.
Do I need both Open Graph and Twitter image tags?
The Open Graph image is the core shared image reference. The example also supplies twitter:card and twitter:image to request a large image card. Check the current preview on the platform you target.
Why should I add image alt text?
og:image:alt provides a text description of the image. Describe what the image conveys rather than repeating the page title.


