Browse docs

Link previews & social cards

Technical reference for how requests are split between preview bots and normal browsers. For a plain-language summary, see How your links work.

In short: Normal browsers get a redirect to your real page. Apps that generate link previews get a small HTML page with Open Graph tags (title, description, image) that you configure — not a dump of your destination URL into the description.

Core behavior

For each GET /s/[slug] request, the handler inspects User-Agent. If it matches a known link-preview / social crawler pattern in lib/crawler-uas.ts, the response is 200 HTML with Open Graph and Twitter meta tags built from stored botTitle, botDescription, and optional ogImageUrl.

Otherwise the client is treated as a normal browser: 302 Found to destinationUrl (after merging stored UTM parameters — see Analytics).

Canonical and og:url

Bot HTML sets og:url and link rel="canonical" to the short URL on your app origin (e.g. https://your.app/s/slug), not the destination.

Non-negotiable content rules

Never put the real destinationUrl (or an equivalent leak of the final landing URL) into bot-facing HTML or og:description. Crawlers and preview cards must only expose the title, description, and image you intend for the tile.

The quick shorten path uses a generic description string so the true URL never appears in og:description.

Inactive and expired links

If isActive is false or expiresAt is in the past, the route returns 410 Gone (both bots and humans).

Caching

Bot responses use Cache-Control: public, max-age=300, ... and Vary: User-Agent. Human redirects use private, no-store.

Extending crawler detection

Adding UA substrings reduces false negatives (bots hitting 302 and seeing Location), but can increase false positives (real users misclassified as bots). Change lib/crawler-uas.ts carefully and regression-test with real preview debuggers. See Crawler checklist and Troubleshooting.