For ten years the link-preview conversation had two columns: Facebook and Twitter. In 2026 it has at least five — and three of them (Threads, Bluesky, and the entire Mastodon federation) read Open Graph slightly differently, cache it on different schedules, and silently drop your preview card for reasons that never show up in a validator. If your share rate dropped after you stopped chasing Twitter’s API and started posting where the conversation actually is, this is probably why.
This is a working field guide to what each platform’s fetcher does in 2026, the five quirks that kill preview cards, and a single copy-paste meta block that renders correctly on all three without per-platform branching.
Threads cleared 320 million monthly actives this spring and turned on full ActivityPub federation by default. Bluesky crossed 50 million accounts after the December onboarding wave and is the de-facto replacement for tech Twitter. Mastodon’s top 200 instances now host roughly 12 million active accounts, and ActivityPub bridges mean a post from any of them can land in a feed on the others.
Practically: the same blog post URL is now being fetched by three preview services with three different fetcher behaviors, and the worst-case rendering is the one your audience sees. Optimize for the strictest fetcher and the rest take care of themselves.
The underlying protocol is the same on all three — an HTTP GET that parses the HTML <head> for Open Graph and Twitter Card meta tags — but the way that GET is made differs in ways that matter:
| Platform | Fetcher timeout | JS executed? | User-Agent | Cache lifetime |
|---|---|---|---|---|
| Threads | ~5s | No | meta-externalagent | ~7 days (per-edge) |
| Bluesky | ~3s | No | Bluesky Cardyb/1.0 | ~24 hours |
| Mastodon | ~10s | No | Mastodon/x.y.z (per instance) | ~14 days (per instance) |
None of them execute JavaScript. Every meta tag you care about has to be in the static HTML response, before any client-side hydration runs. This is the single most common failure mode in 2026: pages that work fine on Twitter (which never executed JS either) and on Slack (which has a much longer fetcher window) silently render as bare URLs on Bluesky because the 3-second budget expired before the framework finished serializing the head.
Drop this in your HTML <head> and you cover all three platforms plus the Twitter, Facebook, Slack, Discord, iMessage, and LinkedIn fetchers as a bonus. Inline-replace the four {...} values per page:
<!-- Open Graph (Threads, Bluesky, Mastodon, FB, LinkedIn, Slack, Discord) -->
<meta property="og:title" content="{Page title, <= 60 chars}" />
<meta property="og:description" content="{Summary, <= 155 chars}" />
<meta property="og:image" content="https://example.com/og/{page}.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content="{Describe the image, <= 100 chars}" />
<meta property="og:url" content="https://example.com/{canonical-path}/" />
<meta property="og:type" content="article" />
<meta property="og:site_name" content="{Brand name}" />
<!-- Twitter Card (used as fallback by Threads on iOS) -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="{Page title, <= 60 chars}" />
<meta name="twitter:description" content="{Summary, <= 155 chars}" />
<meta name="twitter:image" content="https://example.com/og/{page}.png" />
Three things in that block are non-obvious and matter a lot in 2026. First, og:image:width and og:image:height are how Bluesky decides between the small and large card layouts — omit them and you get the small card every time, which has roughly half the click-through. Second, og:image:alt is the only one of these tags that affects accessibility on Mastodon and is read aloud by screen readers in feeds. Third, og:site_name shows up as the byline on Threads but as the source-attribution chip on Bluesky — both worth claiming.
Paste your URL, title, and description — get a clean, validator-passing meta block with OG, Twitter Card, JSON-LD, and canonical tags ready to drop into <head>. Runs entirely in your browser.
If your framework injects og:* tags after hydration (React Helmet without SSR, Vue Meta in SPA mode, Next.js with 'use client' at the top of the page), Bluesky will time out and Mastodon will fetch only the bare HTML shell. The fix is server-side rendering or static export of the head — not just the body. Facebook’s sharing debugger still shows the same response Bluesky and Threads see, so use it to verify.
An og:image over about 5 MB will be dropped by Bluesky and downsampled to a placeholder by Threads. Cap to under 1 MB, encode as JPEG or compressed PNG, and serve from a CDN. If your OG images render dynamically (Next.js @vercel/og, Cloudflare workers), pre-cache the response so the first fetcher does not hit a 4-second cold start.
Each Mastodon instance caches preview cards independently for up to 14 days. If you republish a post and the image looks wrong, do not assume your tags are broken — append ?v=2 (or any tracking param) to the URL when sharing. Each instance treats it as a new resource and refetches.
Bluesky crops og:image to a 1.91:1 aspect ratio in feed regardless of what you declare. A 1200×630 image (already 1.91:1) renders identically. A 1200×1200 square gets its top and bottom 23 percent sliced off. Keep critical text and faces in the centered 1200×630 safe zone — the same zone used for Twitter Cards and Threads, so this is a one-template-fits-all problem.
If og:url uses HTTPS and an inbound share link uses HTTP (or has a trailing slash difference), Threads sometimes shows the bare link instead of the card. Always include a self-referencing <link rel="canonical"> matching og:url exactly, and redirect HTTP → HTTPS at the edge before the fetcher reaches the page.
None of these are required for link previews, but they unlock other federated-social behaviors:
<link rel="me" href="https://mastodon.social/@you" /> — lets you put a verified green checkmark next to your URL in your Mastodon profile.<link rel="webfinger" href="..." /> — lets your domain be used as a fediverse handle (@you@example.com).<meta name="fediverse:creator" content="@you@mastodon.social" /> — emerging tag that some Mastodon instances now surface as a byline on shared links. Not universally supported but harmless.Threads is testing an extended card format that pulls additional structured data — author, published date, reading time — from a page’s JSON-LD Article schema. It is not live for every account yet, but pages that already have valid JSON-LD inherit the richer card automatically when rollout hits. This is the second-cheapest 2026 social win after fixing your OG tags: most CMSs ship valid Article schema, and the same JSON-LD also feeds Google AI Overviews and Bing’s generative answers.
For most blogs and SaaS landing pages in 2026, the entire problem reduces to: render Open Graph and Twitter Card tags server-side, point to a 1200×630 image under 1 MB, set og:image:width and og:image:height so Bluesky picks the large card, and add og:image:alt for Mastodon’s screen readers. Everything else (instance caching, fetcher timeouts, ActivityPub niceties) is optimization. Get that base right and your link previews will work on Threads, Bluesky, Mastodon, and every legacy platform that still bothers fetching them.
If you would rather not hand-write the block, the SEO Meta Generator outputs the exact template above with your fields plugged in, including the JSON-LD Article schema that unlocks Threads’ context cards. Paste your URL, title, description, and image URL — copy the result into <head> — you are done.
Browser-only. No upload. Outputs the exact meta block above, validator-clean, ready to paste into your HTML <head>.