OpsKitPro logo
OpsKitPro_
Back to blog
Guide2026-07-088 min

Open Graph and Twitter Cards: A Practical Guide to Social Previews

Why do social preview cards break across X, Telegram, WhatsApp, and other platforms? Learn how metadata, crawler behavior, absolute URLs, caching, and platform differences affect link previews.

This article reads through design, implementation, and usage in one flow.
Open tool
Open Graph and Twitter Cards: A Practical Guide to Social Previews
About the article

This page is organized as a full read-through, from background to implementation and usage.

Article body

Why do social media preview cards break so often? You paste a link into X (Twitter), Telegram, or WhatsApp, and the image is missing, the title is wrong, or the card simply fails to render.

Most tutorials focus purely on HTML syntax: "Just add these <meta> tags and you are done." However, in production, ensuring social previews work is less about knowing the tags and more about understanding crawler behavior, caching, and network routing.

This guide breaks down the core concepts of Open Graph and Twitter Cards, followed by a production-oriented troubleshooting checklist.

Open Graph vs. Twitter Cards: The Basics

There are two primary protocols you need to satisfy to cover 99% of social platforms:

  1. Open Graph (OG): Originally developed by Facebook, this relies on the property attribute (e.g., <meta property="og:title" ...>). It is the universal standard used by Facebook, LinkedIn, WhatsApp, and Telegram.
  2. Twitter Cards: X's proprietary protocol, which relies on the standard HTML name attribute (e.g., <meta name="twitter:title" ...>).

The Fallback Mechanism

If X (Twitter) crawls a page and cannot find a specific twitter: tag (like twitter:title or twitter:description), it will automatically fall back to the corresponding og: tag.

However, there is one critical exception: twitter:card.

Open Graph has no equivalent to twitter:card. If you want a rich, large image preview on X, you MUST explicitly declare:

<meta name="twitter:card" content="summary_large_image">

Without this, X will default to a small text-only card or no preview at all, regardless of your OG image.

Why Social Previews Really Break: The Crawler Layer

1. Scrapers Do Not Execute JavaScript

Facebook, X, Telegram, and WhatsApp use simple server-side scrapers. They do not execute JavaScript.

If you use a client-side Single Page Application (SPA) framework (like raw React or Vue) and inject your <meta> tags via document.head.appendChild on mount, the crawler will see an empty <head>. Your og:image and og:title must be present in the raw HTML response generated by Server-Side Rendering (SSR), Static Site Generation (SSG), or an edge gateway.

2. The Absolute URL Rule

You must use absolute URLs (https://...) for your images.

<!-- WRONG ❌ -->
<meta property="og:image" content="/images/share.jpg">
 
<!-- CORRECT ✅ -->
<meta property="og:image" content="https://example.com/images/share.jpg">

Facebook's crawler explicitly forbids relative URLs. Telegram's crawler will silently fail if given a relative path.

Wait, but I used a relative path and Telegram still showed an image? If Telegram fails to resolve the og:image, its crawler will aggressively scan the page's <body> and blindly scrape whatever <img> it thinks is large enough to be a fallback. This is a failure state, not a feature. Always use absolute URLs to guarantee the correct image is chosen.

3. The Image Must Be A Direct Link

Avoid placing your og:image behind a 301 or 302 redirect. Telegram, in particular, is notorious for abandoning the image fetch if it hits a redirect chain. The URL you provide in the metadata should return an HTTP 200 with the raw image bytes.

The Great Cache Nightmare

You update your og:image, deploy the site, paste the link into Telegram... and the old image still appears.

Social platforms cache preview metadata aggressively on their own servers to save bandwidth.

  • Facebook: Caches aggressively. You can force a refresh using their Sharing Debugger.
  • X (Twitter): Caches persistently. In 2022, X retired its Card Validator preview feature and removed the ability to manually clear the cache.
  • Telegram / WhatsApp: Cache durations are undocumented and can last for extended periods.

How to Force a Cache Refresh

Since X and messaging apps no longer provide reliable "clear cache" tools, you must trick them into believing the URL is entirely new.

The Solution: Append a content-based query string to your image URL.

<!-- The scraper sees this as a brand new image and fetches it immediately -->
<meta property="og:image" content="https://example.com/share.jpg?v=a1b2c3d4">

Do not use a raw deployment timestamp, as that will force the crawlers to re-fetch identical images on every build. Instead, use a hash of the image file (like an ETag) so the URL only changes when the actual image content changes.

The Preview Image Exists, But Can The Crawler Fetch It?

This is the most common blind spot for developers. You inspect the HTML, the og:image is there, the absolute URL is correct, yet the social card is blank.

Good diagnostics require checking the network boundary:

  1. Cloudflare Bot Fight Mode: Is your WAF aggressively blocking automated traffic? Social crawlers identify themselves via User-Agents like facebookexternalhit, Twitterbot, or TelegramBot. If your firewall blocks them, the card fails.
  2. robots.txt: Does your robots.txt disallow /images/? Some scrapers strictly obey these rules and will refuse to fetch your cover.
  3. Content-Type: If your server returns application/octet-stream instead of image/jpeg or image/png, platforms like WhatsApp will discard the image.
  4. Image Size Limitations: Large images or unsupported formats (like modern WebP or heavy GIFs) may fail or behave inconsistently across platforms and crawlers. Stick to highly compressed JPEGs or PNGs.

Next.js Implementation Example

If you are using the Next.js App Router, configuring standard social cards across all platforms is seamless using the unified Metadata API:

import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: 'Your Page Title',
  description: 'Your page description.',
  openGraph: {
    title: 'Your Page Title',
    description: 'Your page description.',
    url: 'https://example.com',
    siteName: 'Your Site',
    images: [
      {
        url: 'https://example.com/og-image.jpg?v=hash123',
        width: 1200,
        height: 630,
      },
    ],
    locale: 'en_US',
    type: 'website',
  },
  twitter: {
    card: 'summary_large_image', // CRITICAL
    title: 'Your Page Title',
    description: 'Your page description.',
    images: ['https://example.com/og-image.jpg?v=hash123'],
  },
}

Production Troubleshooting Checklist

Before launching a high-stakes campaign, run through this checklist:

  • [ ] Does curl -s https://your-site.com | grep "og:image" return a tag? (Proves SSR works).
  • [ ] Is the image URL an absolute https://... path?
  • [ ] Does curl -I https://your-site.com/image.jpg return HTTP 200 (no redirects) and Content-Type: image/jpeg?
  • [ ] Is the image file size highly compressed to ensure broad compatibility?
  • [ ] Is twitter:card explicitly set to summary_large_image?
  • [ ] Are crawler bots whitelisted in your WAF/Firewall?

Social previews are not just aesthetic—they drastically impact Click-Through Rates (CTR). By understanding how crawlers fetch and cache your data, you can stop fighting the HTML tags and start diagnosing the actual network path.

Need to verify if your headers and response status are bot-friendly? Run your URL through the OpsKitPro Website Check to analyze the raw HTTP response before you share.