The Facebook Preview Was Broken. The Meta Tags Were Innocent.

By ShareScan ยท EngineeringPublished 6 min read

We chased a Facebook/Messenger preview bug through Open Graph tags, image URLs, robots.txt, and range requests. The fix was hiding in Cache-Control.

Open Graph checker report for a Facebook preview debugging story

We had one of those useful little humiliations this week.

A ShareScan blog post looked fine in the browser. The Open Graph tags were there. The image was the right size. The URL was public. The page had already been through the kind of checklist we usually tell other people to run.

Then the link went into a Facebook/Messenger message and the preview did not behave.

Naturally, our first instinct was to look very serious and say things like "platform cache" and "crawler variance." This is engineer for "please let the problem be somewhere else."

It was not somewhere else.

The Symptom

The page in question was a blog article about Open Graph images not showing in Slack, LinkedIn, and X. In other words, a page about broken social previews had a broken social preview.

That is not a bug. That is content strategy with commitment.

The important part: the page itself loaded correctly. The title, description, canonical URL, and image metadata all appeared in the server-rendered HTML. The image URL opened directly. Nothing obvious was missing.

So we started with the boring checks.

What We Checked First

Before changing anything, we fetched the live production URL the way a crawler might see it.

We confirmed:

  • the page returned 200
  • the Open Graph tags existed in the initial HTML
  • og:type was article
  • og:title, og:description, og:url, and og:image were present
  • the image was a public HTTPS JPEG
  • the image was 1200 x 630
  • the image returned image/jpeg
  • range requests worked
  • robots.txt was not blocking the path
  • the response was the same for a Facebook crawler user agent

At this point, the metadata looked annoyingly correct.

This is the dangerous moment in preview debugging. When the tags look correct, it is tempting to start nudging copy, swapping the image, adding one more property, or refreshing the same URL in a debugger until someone suggests lunch and the issue becomes tomorrow's problem.

Instead, we kept reading the response.

The Header That Looked Wrong

The HTML response had this cache header:

CodeCode example
http
1cache-control: no-store, private, max-age=0, must-revalidate

That is a perfectly reasonable kind of header for private application pages. Dashboards, account screens, billing areas, and anything user-specific deserve to be treated carefully.

But this was not a billing page. It was a public blog post that we wanted crawlers, link expanders, and social platforms to fetch, understand, and cache predictably.

So our public article was effectively saying:

Hello, please build a nice share preview from this page. Also, please treat this response like it contains someone's tax return.

Not ideal.

Why Cache-Control Matters For Previews

Social preview crawlers are not normal readers. They do not sit down with a coffee, open DevTools, wait for hydration, admire the prose, and forgive a few odd headers because the intent was good.

They fetch the URL, read the response, collect metadata, fetch the image, and decide what to cache for future shares. Headers are part of that conversation.

no-store says the response should not be stored. private says shared caches should not store it. max-age=0 says it is immediately stale. None of those are the kind of signals we want on stable public content.

This does not mean Cache-Control is the cause of every Facebook preview problem. It is not magic. It will not rescue missing og:image tags, blocked assets, oversized images, bad redirects, or a stale platform cache.

But in our case, the page was public, the metadata was valid, and the cache policy was sending the wrong message.

The Fix

We added route-level headers for public blog HTML:

CodeCode example
http
1cache-control: public, max-age=300, s-maxage=300, stale-while-revalidate=300

That gives browsers a short cache window, gives shared caches a clear public policy, and still lets the content refresh quickly when the article changes.

We applied it to:

  • individual blog posts
  • the blog index
  • tag archive pages

We did not apply it to private app routes or API responses that should remain uncached. This was not a heroic global header change. It was a small correction for public HTML that should behave like public HTML.

What We Should Have Done Earlier

The honest answer: looked at headers sooner.

Our debugging order was not terrible. It is still right to verify the raw HTML, the image URL, and crawler access first. But once those passed, the cache policy should have moved up the suspect list faster.

Here is the cleaner flow we would use next time:

  1. Fetch the final public URL with curl -I.
  2. Confirm it returns 200 without odd redirects.
  3. Check the raw HTML for Open Graph and Twitter Card metadata.
  4. Open the og:image URL directly.
  5. Confirm image dimensions, content type, and file size.
  6. Check robots.txt and obvious crawler blocks.
  7. Read Cache-Control on both the page and the image.
  8. Refresh the relevant platform cache only after the source looks clean.

If you skip straight to the platform debugger while the source response is still wrong, you are just asking a third party to cache your mistake with confidence.

The Small Lesson

Broken social previews are rarely about one tag.

They are usually the result of a few systems meeting briefly and not agreeing on what reality is. Your CMS has one version. Your frontend renders another. Your CDN remembers yesterday. The platform crawler shows up with very little patience. Then someone pastes the link into a chat and asks why the thumbnail is missing.

In our case, the meta tags were innocent. The image was innocent. The crawler was probably doing something reasonable with a response that did not look like public, cacheable content.

We fixed the header. Then we verified locally with the production build and checked the response again.

The slightly bruising part is that this is exactly the kind of thing ShareScan exists to make easier to spot. Which is, at least, a tidy product lesson wrapped inside a small public faceplant.

A Quick Cache-Control Checklist For Shareable Pages

For public articles, landing pages, docs, and campaign pages, check:

  • does the HTML return a public cache policy?
  • is the cache window short enough for edits to appear quickly?
  • does the CDN or edge cache have a clear s-maxage policy?
  • are private app routes still protected with no-store?
  • does the image URL have stable, crawler-friendly headers?
  • do you test the final production URL, not preview or staging?

The goal is not to cache everything forever. The goal is to make public pages look public, private pages stay private, and crawlers stop receiving mixed signals.

If your preview is still wrong after that, use the link preview debugger, then follow the Open Graph image refresh checklist. And if the image is huge, start with the Open Graph image size guide before you blame the platform.

FAQ

It can contribute to preview problems, especially when public HTML is served with private or no-store cache headers. It is not the only possible cause, but it is worth checking after metadata, image access, redirects, and robots rules.

Usually no. Public, shareable articles normally benefit from a short public cache policy. Keep no-store for private, personalized, or account-specific pages.

It depends on your publishing model, but a short public policy such as `public, max-age=300, s-maxage=300, stale-while-revalidate=300` is a practical starting point for frequently edited public content.

Often, yes. First fix the source response. Then use the relevant platform debugger or wait for the platform cache to refresh. Refreshing before the source is clean just teaches the platform the wrong thing again.

Related articles

Put the guide into practice

Check the links your team shares.

Check preview images, titles and descriptions on up to 10 pages. Review the findings, then choose what to monitor.

No account or card. Choose weekly follow-ups by email or Slack after your scan.

Paste a page, a domain, or up to 10 URLs, one per line.