DEV Community

eatyou eatyou
eatyou eatyou

Posted on

"Debugging Open Graph Tags: Why Your Link Previews Look Wrong"

You added all the Open Graph tags. You deployed. You paste the URL into Slack or Twitter — and the preview is wrong, blank, or showing an old image from three months ago.

Here is a systematic guide to diagnosing and fixing broken link previews.

Step 1: Check if Your Tags Are Actually in the HTML

First, confirm the tags exist and are correct in the raw HTML. Do not trust your browser's "view source" — that shows the initial HTML, not what JavaScript may inject later.

Use curl to see exactly what a crawler receives:

curl -A "facebookexternalhit/1.1" -L "https://yoursite.com/page" | grep -i "og:"
Enter fullscreen mode Exit fullscreen mode

The -A flag sets the User-Agent to Facebook's crawler. Some sites serve different HTML to bots, so this matters.

You should see output like:

<meta property="og:title" content="Your Title" />
<meta property="og:image" content="https://yoursite.com/image.png" />
Enter fullscreen mode Exit fullscreen mode

If you see nothing, the tags are either missing or rendered by JavaScript (see below).

Step 2: The JavaScript Rendering Problem

Open Graph tags must be in the initial server-rendered HTML. Most social crawlers do not execute JavaScript.

If your site is a React SPA that injects meta tags via document.title or a library like react-helmet without server-side rendering, crawlers will never see your OG tags.

Symptoms:

  • Tags look correct in browser DevTools
  • curl returns no OG tags
  • Link previews are blank

Solutions:

  • Enable SSR or static generation (Next.js, Nuxt, Astro, etc.)
  • Use a prerendering service (Prerender.io, Rendertron)
  • Set tags server-side before sending the HTML response

Quick test: compare curl output vs browser source:

# What crawlers see
curl -s "https://yoursite.com" | grep "og:image"

# If different from browser source, JS rendering is your problem
Enter fullscreen mode Exit fullscreen mode

Step 3: Platform Cache Is Showing Old Data

This is the most common source of confusion. You fixed your tags but the preview still shows old content. That is because every platform caches OG data independently, often for days.

Force a cache refresh:

Platform How to Refresh
Facebook Sharing Debugger → Scrape Again
LinkedIn Post Inspector → Inspect
Twitter/X Card Validator
Slack Re-paste the URL; or use / command to unfurl
Discord Append ?v=2 to the URL temporarily
Telegram Bot Father has no manual refresh; usually updates within hours

Key insight: even after you refresh Facebook's cache, users who already saw the old preview in a post will still see the old version. The cache refresh only affects new shares.

Step 4: Check Your Image URL

The og:image URL is the most common source of broken previews. Run through this checklist:

Is it absolute?

<!-- Wrong -->
<meta property="og:image" content="/images/og.png" />

<!-- Right -->
<meta property="og:image" content="https://yoursite.com/images/og.png" />
Enter fullscreen mode Exit fullscreen mode

Is it HTTPS?

HTTP images often fail silently on HTTPS pages. Check:

curl -I "https://yoursite.com/images/og.png"
# Should return 200, not 301/403/404
Enter fullscreen mode Exit fullscreen mode

Is it accessible without authentication?

Crawlers cannot log in. Images behind auth walls, VPN, or IP allowlists will fail.

Does it meet size requirements?

Platform Minimum Recommended
Facebook 200×200px 1200×630px
Twitter 144×144px 1200×675px
LinkedIn 200×200px 1200×627px

Images below the minimum are silently ignored.

Is the Content-Type correct?

curl -sI "https://yoursite.com/og.png" | grep content-type
# Should be: content-type: image/png or image/jpeg
Enter fullscreen mode Exit fullscreen mode

Some servers return application/octet-stream for images, which crawlers reject.

Step 5: Encoding and Special Characters

If your title or description contains special HTML characters, they must be properly escaped:

<!-- Wrong — will break the tag -->
<meta property="og:title" content="5 Tips & Tricks for <Developers>" />

<!-- Right -->
<meta property="og:title" content="5 Tips &amp; Tricks for &lt;Developers&gt;" />
Enter fullscreen mode Exit fullscreen mode

Also watch out for:

  • Smart quotes (" ") — fine in content, but not as attribute delimiters
  • Newlines inside attribute values — break the tag
  • Missing closing /> — can invalidate subsequent tags

Step 6: Duplicate Tags

If you have a CMS, template engine, or third-party plugin, you may end up with duplicate OG tags. Most platforms use the first match, but some use the last, and behavior varies.

curl -s "https://yoursite.com" | grep -c "og:title"
# Should be 1, not 2 or 3
Enter fullscreen mode Exit fullscreen mode

Common causes: WordPress Yoast + theme hardcoded tags, or a header partial included twice.

Step 7: The og:url Mismatch

If your og:url does not match the actual URL being shared, Facebook in particular will associate the metadata with the canonical URL, not the shared URL. This causes weird de-duplication where multiple different URLs show the same preview.

<!-- Make sure this exactly matches the canonical URL -->
<meta property="og:url" content="https://yoursite.com/exact-page-url" />
Enter fullscreen mode Exit fullscreen mode

A Debugging Script

Here is a Node.js script that checks OG tags for any URL and reports common issues:

async function debugOG(url) {
  const res = await fetch(url, {
    headers: { 'User-Agent': 'facebookexternalhit/1.1' }
  });
  const html = await res.text();

  const get = (prop) => {
    const m = html.match(
      new RegExp(`<meta[^>]+property=["']${prop}["'][^>]+content=["']([^"']+)["']`, 'i')
    ) || html.match(
      new RegExp(`<meta[^>]+content=["']([^"']+)["'][^>]+property=["']${prop}["']`, 'i')
    );
    return m ? m[1] : null;
  };

  const title = get('og:title');
  const description = get('og:description');
  const image = get('og:image');
  const ogUrl = get('og:url');

  console.log('og:title:', title || '❌ MISSING');
  console.log('og:description:', description || '⚠️  MISSING');
  console.log('og:image:', image || '❌ MISSING');
  console.log('og:url:', ogUrl || '⚠️  MISSING');

  if (image) {
    if (!image.startsWith('https://')) {
      console.warn('⚠️  og:image is not HTTPS:', image);
    }
    if (image.startsWith('/')) {
      console.error('❌ og:image is a relative URL:', image);
    }
  }

  if (ogUrl && ogUrl !== url) {
    console.warn('⚠️  og:url does not match requested URL');
  }
}

debugOG('https://yoursite.com/page');
Enter fullscreen mode Exit fullscreen mode

Tools That Do This For You

If you would rather not parse HTML yourself, there are APIs that handle the full extraction pipeline — including redirect following, charset detection, and relative URL resolution.

LinkPeek is a free option:

curl "https://linkpeek-api.linkpeek.workers.dev/v1/preview?url=https://yoursite.com/page&key=YOUR_KEY"
Enter fullscreen mode Exit fullscreen mode

Returns:

{
  "title": "Your Page Title",
  "description": "Your description",
  "image": "https://yoursite.com/og-image.png",
  "favicon": "https://yoursite.com/favicon.ico"
}
Enter fullscreen mode Exit fullscreen mode

Useful for sanity-checking what an external crawler actually sees from your page.

Summary Checklist

When a link preview looks wrong, work through this list in order:

  • [ ] Tags exist in server-rendered HTML (not just JS-rendered)
  • [ ] og:image is an absolute HTTPS URL
  • [ ] Image is accessible without auth and returns the right Content-Type
  • [ ] Image meets minimum size requirements (1200×630px recommended)
  • [ ] Special characters are HTML-encoded
  • [ ] No duplicate og:title or og:image tags
  • [ ] og:url matches the canonical URL
  • [ ] Platform cache has been force-refreshed

Nine times out of ten, it is one of the first three items on this list.

Top comments (0)