DEV Community

Cover image for Why Your og:image Works in the Browser but Not for Crawlers: Relative Paths, Redirects and Wrong Content-Types
Abid Ali
Abid Ali

Posted on

Why Your og:image Works in the Browser but Not for Crawlers: Relative Paths, Redirects and Wrong Content-Types

There is a special kind of frustration reserved for the image that works. You open https://yoursite.com/og/launch.png in a tab, it loads instantly, it looks great. You paste the page into LinkedIn and get a headline, a domain, and no picture.

Most write-ups about broken link previews stop at "make sure og:image is set". This post is about the next layer: the tag is set, the file exists, and the crawler still can't use it. Almost every case I've debugged comes down to one of six URL-level problems. Each one has a quick way to prove it from the terminal.

First, stop testing with your browser

Your browser is the worst possible witness here. It has your cookies, it follows any redirect, it resolves relative paths against whatever page you're on, and it happily renders an image even when the server lies about what it is.

A preview crawler has none of that context. It gets one HTML response, pulls a string out of a <meta> tag, and makes a single cold request for that string. So test the same way:

# 1. What does the HTML actually say?
curl -s https://yoursite.com/blog/launch | grep -i 'og:image'

# 2. What happens when a stranger requests that exact value?
curl -sI -A "facebookexternalhit/1.1" "https://yoursite.com/og/launch.png"
Enter fullscreen mode Exit fullscreen mode

The second command is the one that matters. You want a 200, an image/* content type, and a reasonable content-length. Anything else is your bug.

Problem 1: the value is a relative path

<!-- Looks fine, often fails -->
<meta property="og:image" content="/og/launch.png" />
Enter fullscreen mode Exit fullscreen mode

The Open Graph protocol expects a full URL. Some crawlers resolve relative paths against the page URL, some don't, and some resolve them against the URL before redirects, which is a different host than you think. Don't make the crawler guess:

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

In frameworks, this usually comes from a missing base URL setting. In Next.js that's metadataBase; in Astro it's site in the config; in Hugo it's baseURL. If the base is unset or set to localhost in production, you ship relative or localhost URLs without noticing, because your own browser on your own site "fixes" them.

A quick grep catches it in CI:

curl -s https://yoursite.com/blog/launch \
  | grep -oE 'og:image" content="[^"]+' \
  | grep -vE 'content="https://' && echo "og:image is not absolute"
Enter fullscreen mode Exit fullscreen mode

Problem 2: the image URL redirects somewhere unfriendly

Redirects are not automatically fatal, but every hop is a chance to break. Common chains I've seen:

  • http:// → https:// → www. → CDN signed URL
  • an image path that 302s to a login page for anyone without a session
  • an old asset path that redirects to the homepage (so the crawler gets HTML)

Follow the chain and look at where it lands:

curl -sIL -A "LinkedInBot/1.0" "https://yoursite.com/og/launch.png" \
  | grep -iE '^(HTTP|location|content-type)'
Enter fullscreen mode Exit fullscreen mode

If you see more than one hop, publish the final URL directly in the tag. If the last hop is text/html, the crawler received a web page, not a picture.

Problem 3: the server lies about the content type

This one is sneaky because browsers sniff content and forgive you. Crawlers often don't. Typical causes:

  • A storage bucket serving .png files as application/octet-stream.
  • A framework catch-all route that returns your SPA shell with a 200 for any path, including a mistyped image path. The "image" is your index.html.
  • A file uploaded with a .jpg extension that is actually WebP or AVIF.
curl -s "https://yoursite.com/og/launch.png" -o /tmp/og && file /tmp/og
Enter fullscreen mode Exit fullscreen mode

If file says HTML document, you found the problem. Also, stick to JPG, PNG, or WebP for preview images. AVIF and SVG support across preview crawlers is uneven, and an SVG with no explicit pixel dimensions gives a crawler nothing to size the card with.

Problem 4: the URL expires

Signed URLs from S3, GCS, Cloudinary or an image CDN often carry an expiry. That's great for private files and terrible for og:image. The tag gets scraped once, maybe again a week later, and by then the signature is dead.

Symptoms: the preview works on day one and silently disappears for later shares. If your og:image contains X-Amz-Expires, Expires=, token= or similar, give the image a stable public URL instead.

Problem 5: the file is too big or too small

Two opposite failures:

  • Too big. A 6 MB PNG straight out of a design tool can time out the crawler's fetch. Some apps are stricter than others about file size, and chat apps in particular tend to give up quickly. Export a compressed JPG or WebP around 1200×630; it usually lands well under 300 KB.
  • Too small. A 150×150 logo is technically an image, but most platforms won't use it for a large card, or they'll fall back to a tiny thumbnail.
# Size in bytes and pixel dimensions (needs ImageMagick)
curl -s "https://yoursite.com/og/launch.png" -o /tmp/og.png \
  && ls -l /tmp/og.png && identify /tmp/og.png
Enter fullscreen mode Exit fullscreen mode

Problem 6: same URL, new image

You fixed the picture, uploaded it to the same path, and the old one keeps showing up. That's not a crawler failure; it's a cache doing its job. Platforms store the image they scraped, and many chat apps have no refresh button at all.

Version the URL whenever the image changes:

<meta property="og:image" content="https://yoursite.com/og/launch.png?v=3" />
Enter fullscreen mode Exit fullscreen mode

Or bake a content hash into the filename, which most build tools already do for other assets.

Checking all six in one pass

Running these curl commands for every page gets old. I built a free tool for exactly this step: the OG Scope OG image checker. You paste a public URL and it shows the og:image value exactly as published next to the resolved absolute URL it actually requested, whether a real image came back, and the pixel size when the file format exposes it. It warns when the URL isn't absolute, when the file can't be loaded, when a side is under 200 px, or when it's below the usual 1200×630 size.

A couple of honest limits: it fetches the public HTML server-side and doesn't run your JavaScript (neither do most social crawlers, which is the point), and it can't tell you what Facebook or LinkedIn currently has cached. For stale caches, use the platform's own tools: Facebook's Sharing Debugger and LinkedIn's Post Inspector.

A short checklist to keep

Before you share a link that matters:

  1. og:image is an absolute https:// URL.
  2. That exact URL returns 200 with an image/jpeg, image/png or image/webp content type, with no login and no long redirect chain.
  3. The URL doesn't expire.
  4. The file is roughly 1200×630 and comfortably under a few hundred KB.
  5. When the image changes, the URL changes too.

Get those five right and the remaining preview bugs are usually about caching or text, which are much easier problems to have.


Abid Ali is a web developer in Pakistan who builds small, free tools, including OG Scope, an Open Graph and link preview checker.

Top comments (0)