Marketing · 9 min read · Updated September 15, 2026
Why Your Link Preview Breaks When You Shorten a URL
You paste a link into Slack and get a clean card: headline, summary, image. You shorten the same link, paste it again, and get a bare blue URL. Nothing about the destination changed, so the obvious conclusion is that the shortener broke it. Usually it did not, and the difference between those two outcomes is worth understanding before you start deleting things.
This guide covers what a platform actually does when it sees a URL, why a redirect normally preserves the card, the four specific ways a preview genuinely does break, and how to tell in under a minute which one you are looking at.
A preview is a separate visit, not part of the click
When you paste a URL into Slack, LinkedIn, iMessage or a post composer, the platform sends its own automated request to that URL, before anybody clicks anything. That request comes from the platform servers, not from your browser, and it identifies itself with a distinctive user agent such as facebookexternalhit, Slackbot, LinkedInBot or Twitterbot.
The scraper downloads the HTML and reads a handful of <meta> tags out of the <head>, chiefly the Open Graph set: og:title, og:description, og:image and og:url. It builds the card from those, caches the result, and discards the rest of the page. It does not run your JavaScript, it does not scroll, and in most cases it will not come back on its own.
Why shortening a link normally keeps the card
A short link is a redirect, and scrapers follow redirects exactly as browsers do. The scraper requests the short URL, receives a 302 with a Location header, requests the destination, and reads the destination Open Graph tags. The card it builds describes the destination, which is the behavior you want. See 301 vs 302 redirects for what the status code itself changes.
So in the ordinary case, shortening a link is invisible to the preview. If the destination has good tags, the short link previews with those tags. If the card is blank after shortening, the shortener is rarely the cause, and the sections below cover what is.
One thing does genuinely change: the domain shown on the card. Most platforms display the hostname of the final URL, but some show the domain that was pasted. A generic shortener domain looks less trustworthy than your own, which is a real argument for a custom domain rather than a preview problem as such.
The four ways a preview actually breaks
1. The destination is a JavaScript app with an empty shell
This is by far the most common cause and it has nothing to do with shortening. A single-page app serves the same near-empty HTML document for every route and fills it in with JavaScript once the browser runs it. A scraper never runs that JavaScript, so it reads a <head> containing whatever generic tags shipped with the app, or none at all.
The tell is that the card is not merely blank but wrong in a consistent way: every link to the app produces the same card, usually the site name and homepage description, no matter which page you shared. Client-side tag injection does not help here, because the injection happens in JavaScript the scraper never executes.
2. og:image is relative, private, or missing
An og:image value must be an absolute URL including the scheme, publicly reachable with no login, and served over HTTPS. A path like /images/card.png is the single most frequent mistake: it resolves fine in a browser, which knows the current page, and fails for a scraper that is reading tags out of context. Images behind authentication, on staging hosts, or blocked by a firewall fail the same way and produce a card with text but no picture.
3. The platform cached a card from before your fix
Every platform caches aggressively, and cache lifetimes are measured in days or weeks rather than minutes. If a URL was shared once while the tags were broken, every later share of that same URL reuses the broken card until the cache expires or somebody forces a re-scrape. This is the cause whenever the tags look correct in view-source but the card refuses to update.
4. The chain is too long or ends somewhere unexpected
Scrapers follow far fewer hops than browsers do, and some give up after two or three. A short link pointing at another short link pointing at a tracking gateway can exhaust that budget before reaching a page with any tags on it. A chain that ends on a consent wall, an age gate or a login page is worse, because the scraper succeeds and builds an accurate card describing the wall. Check the chain with a redirect checker and flatten it to one hop.
What each platform reads
Open Graph is the common denominator: set it well and nearly everything renders. The X-specific tags are worth adding on top because they control the card shape, and X falls back to Open Graph for anything they do not cover.
| Platform | Reads | Cache behaviour | Force a re-scrape |
|---|---|---|---|
| Open Graph | Long-lived, until re-scraped | Sharing Debugger, Scrape Again | |
| Open Graph | Around a week | Post Inspector | |
| X | twitter:*, falls back to Open Graph | Long-lived | No public validator since 2022; add a query string |
| Slack | Open Graph | Short, roughly an hour | Wait, or add a query string |
| Discord, Telegram, WhatsApp | Open Graph | Varies, generally short | Add a query string |
| iMessage | Open Graph | Per device, long-lived | Add a query string |
The universal escape hatch in that last column is worth spelling out. Appending a throwaway query parameter, for example ?v=2, makes a new URL as far as the cache is concerned, so the scraper fetches it fresh. The page is unchanged, because servers ignore parameters they do not recognise. It is the fastest way to confirm a fix without waiting out a cache.
Fixing the empty-shell case
If your destination is a single-page app, the only durable fix is to make sure the HTML that reaches a scraper already contains the right tags. There are three ways to do that, in ascending order of effort: prerender the page to static HTML at build time, render it on the server at request time, or detect preview scrapers at the edge and serve them a small tag-only document while real browsers get the app.
The third option is the pragmatic one for an app that is otherwise working fine, and it is how ReSlug serves previews for bio pages. A middleware at the edge matches the requesting user agent against a list of known preview scrapers, and for those requests returns a minimal HTML document carrying the tags for that specific page. Everything else falls through to the normal app.
const BOT_UA =
/bot|spider|crawler|slurp|facebookexternalhit|twitterbot|slackbot|discordbot|telegrambot|whatsapp|linkedinbot|embedly|preview/i
if (BOT_UA.test(request.headers.get('user-agent') ?? '')) {
return new Response(metaOnlyHtml(page), {
headers: { 'content-type': 'text/html; charset=utf-8' },
})
}Two cautions if you take this route. Serve scrapers the same substance a human would see, because deliberately showing crawlers different content than users is cloaking and search engines treat it as a violation. And match conservatively: a user agent list is a heuristic, so anything not recognised must fall through to the real app rather than receive a stub.
The tags worth setting
A complete, well-behaved card needs five Open Graph tags and two X-specific ones. Anything beyond these is refinement.
<meta property="og:title" content="Why Your Link Preview Breaks" />
<meta property="og:description" content="What a scraper reads, and the four ways a card goes blank." />
<meta property="og:image" content="https://example.com/cards/previews.png" />
<meta property="og:url" content="https://example.com/guides/link-previews" />
<meta property="og:type" content="article" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Why Your Link Preview Breaks" />- og:image absolute HTTPS URL, 1200 by 630 pixels for the wide card, PNG or JPEG. Avoid SVG and WebP, which several scrapers still refuse.
- og:title around 60 characters. Longer titles are truncated mid-word on narrow cards.
- og:description roughly 150 characters, and it should describe the page rather than repeat the title.
- og:url the canonical absolute URL, which keeps engagement counts attached to one address instead of scattering across parameter variants.
- twitter:card set
summary_large_imagefor the wide image, or omit it entirely to get the small thumbnail layout.
Build the block from a form with an Open Graph tag generator if you would rather not hand-write it, and check the X-specific half with a Twitter card generator.
Debugging a broken card in one minute
Work in this order. Each step rules out one of the four causes, so you stop as soon as something looks wrong rather than changing several things at once.
- Preview the destination URL directly, not the short link, with a social share preview. If the destination card is already broken, the shortener is not involved.
- View source on the destination and search for
og:. If the tags are absent, or identical on every page of the site, you have the empty-shell problem. - Copy the
og:imagevalue and open it in a private window. If it does not load, or it is a relative path, that is your fault before anything else. - Run the short link through a redirect checker and count the hops. More than two, or an unexpected final URL, explains a card that describes the wrong thing.
- Add
?v=2to the URL and paste it again. A correct card now means the tags are fine and you were looking at a cached one.
Frequently asked questions
Do short links break link previews?
Not by themselves. Preview scrapers follow redirects the same way browsers do, so they reach the destination and read its Open Graph tags normally. A blank card after shortening almost always means the destination lacks usable tags, its og:image is unreachable, or the platform cached a broken card earlier.
Why does my preview image not show up?
The usual cause is an og:image value that is a relative path rather than an absolute HTTPS URL, or an image behind a login, on a staging host, or in a format such as SVG that several scrapers refuse. Open the exact og:image value in a private browser window: if it does not load there, it will not load for a scraper.
How do I force a platform to refresh a cached preview?
Facebook and LinkedIn provide tools for this, the Sharing Debugger and Post Inspector respectively. X retired its card validator in 2022. Where no tool exists, append a throwaway query parameter such as ?v=2 to create a URL the cache has not seen, and avoid using a UTM parameter for this because it pollutes your campaign reporting.
Why does every page on my site show the same preview card?
That is the signature of a single-page app. The scraper receives the same near-empty HTML shell for every route and reads whatever generic tags shipped with it, because it never runs the JavaScript that would inject the real ones. Prerendering, server rendering, or serving scrapers a tag-only document at the edge all fix it.
Do I need Twitter card tags if I already have Open Graph?
Not strictly, since X falls back to Open Graph for title, description and image. Add twitter:card anyway, because it is what selects between the small thumbnail and the large image layout, and the large one is usually what you want for an article or a landing page.