A zero-backend, one-page site that turns any supported URL into a clean redirect link. When a friend opens that link on their phone, it tries the native app first, then falls back to the web β no tracking, no analytics, no server.
https://your-domain.com/r/{platform}/{id}
- Pure static β just HTML + CSS + vanilla JS. No build step, no server, no dependencies.
- Config-driven β add or edit platforms by touching
platforms.json. No code changes needed. - Rich link previews β paste a URL and instantly see a preview card with the real title and thumbnail before generating your redirect link.
- Private & safe β runs entirely in the browser. Only domains on the allow-list are accepted; IDs are validated against per-platform regex before any redirect.
- Open source β MIT-licensed. Fork it, self-host it, add your own platforms.
-
Paste a URL (YouTube, Spotify, X, Instagram, Reddit, TikTok, Twitch, LinkedIn, Telegram, WhatsApp, Pinterest, β¦).
-
The page matches the domain against
platforms.json, runs the platform's regex extractors, and pulls out anid. -
A link preview card appears showing the real title and thumbnail of the content.
-
You get a shareable link:
https://your-domain.com/r/{platform}/{id}
When you paste a link, a preview card fetches metadata using a three-strategy waterfall:
| Strategy | How | Platforms |
|---|---|---|
| oEmbed | Platform-native, no rate limits | YouTube, Vimeo, Reddit posts, Apple Music, Pinterest, X/Twitter posts |
| Direct CDN URL | Predictable image URL, zero network fetch | YouTube thumbnails (img.youtube.com), GitHub owner avatar |
| Microlink.io OG scrape | CORS-enabled free endpoint, reads Open Graph tags | Spotify, TikTok, Twitch, Telegram, WhatsApp, Threads, Discord, Maps, and more |
Note: LinkedIn and Instagram actively block all external crawlers at the network level. A rich preview isn't possible for those platforms without their official API.
For WhatsApp / Social Previews (Serverless Edge Function)
Social media crawlers (like WhatsApp, iMessage, and Twitter) do not run JavaScript, which means they cannot natively execute the above preview logic themselves.
To solve this, this project utilizes a Netlify Edge Function (netlify/edge-functions/og-injector.js).
Whenever a crawler fetches a /r/* redirect link, the edge function quickly runs the exact same metadata waterfall logic on Deno, dynamically injects the correct <meta property="og:image"> and <meta property="og:title"> tags into the raw HTML, and returns that HTML to the crawler immediately. This gives you rich previews on WhatsApp while maintaining essentially a "zero-backend" operational structure.
The edge function is opt-in. It is deployed but does nothing unless you explicitly enable it. To enable rich social previews, set the environment variable
ENABLE_OG_PREVIEW=truein your Netlify site settings: Site settings β Environment variables β Add variable βENABLE_OG_PREVIEW=trueLeave it unset (the default) and the function passes through instantly with zero overhead.
- Shows a loading UI with the detected platform.
- Navigates to
deepLink(e.g.youtube://watch?v=ID) to hand off to the native app. - If the page is still foregrounded after a short delay, it navigates to
webLinkas a fallback. - Manual Open in app / Open in browser buttons are always available.
This project is configured for Netlify out of the box. No build step, no backend.
npm i -g netlify-cli
netlify deploy # preview deploy
netlify deploy --prod # production deploy- Push this folder to a GitHub / GitLab / Bitbucket repo.
- In Netlify: Add new site β Import an existing project and pick the repo.
- Leave Build command empty and Publish directory set to
.(already declared innetlify.toml). - Deploy.
Just drag the project folder onto https://app.netlify.com/drop.
netlify.tomlβ publish dir,/r/*β/index.htmlrewrite (status 200, so the URL stays clean), and sensible security + cache headers._redirectsβ same rewrite rule as a fallback, for any tooling that reads it.platforms.jsonandindex.htmlare served withCache-Control: no-cache, so edits go live on the next page load. All other static assets get Netlify's default CDN caching.
Any static server works. For example:
python3 -m http.server 8080
# or
npx serve .Then open http://localhost:8080.
Opening
index.htmldirectly viafile://also works β browsers blockfetch('./platforms.json')underfile://due to CORS, so the app transparently falls back to an inline copy of the config embedded inindex.html. That inline copy is kept in sync by a tiny script (see "Editing the config" below).
platforms.json is the source of truth. On Netlify (or any same-origin static host) the browser fetches it directly, so your edits go live on the next page load β no rebuild.
There's also an inline copy of the same config embedded inside index.html that's only used when fetch('./platforms.json') fails (most commonly when opening the file via file:// locally, which browsers block as CORS). Keep the inline copy in sync with this one-liner whenever you change platforms.json:
node scripts/sync-inline-config.mjsThe script reads platforms.json, validates it, minifies it, and injects it between the INLINE_CONFIG_START / INLINE_CONFIG_END markers in index.html. If you only ever deploy to a real host and don't use file://, you can skip this step entirely.
Open platforms.json and add a new entry under "platforms":
"my-platform": {
"name": "My Platform",
"kind": "Post",
"icon": "β
",
"color": "#ff0080",
"domains": ["myplatform.com", "www.myplatform.com"],
"extractors": [
"myplatform\\.com/post/([A-Za-z0-9]+)"
],
"idValidator": "^[A-Za-z0-9]+$",
"deepLink": "myplatform://post/{id}",
"webLink": "https://www.myplatform.com/post/{id}"
}Field reference:
| Field | Required | Description |
|---|---|---|
name |
yes | Display name. |
kind |
no | Content type shown next to the name (e.g. "Track", "Post"). |
icon |
no | Emoji/short glyph displayed in the platform tile. |
color |
no | Hex color used for the swatch + tile icon background. |
domains |
yes | List of hostnames the URL must match (exact or subdomain). Nothing else is ever accepted β this is the core security check. |
extractors |
yes | Regex patterns tested in order against the full URL. The first capture group becomes the id. |
idValidator |
no | Optional regex the extracted id must fully match. Also enforced on /r/... redirects. |
deepLink |
yes | Template for the app/custom-scheme URL. {id} is substituted. |
webLink |
yes | Template for the browser fallback URL. {id} is substituted. |
- Multiple content types on the same service β add separate entries like
spotify-track,spotify-album,spotify-playlist. Each has its own extractors and link templates. That's how Spotify is configured in the defaultplatforms.json. - Order matters within a platform's
extractorsarray and across platforms: the first match wins. Put the most specific patterns first. - IDs can contain slashes (Reddit posts use
sub/comments/xyz). The app preserves path structure when building and parsing/r/{platform}/{id...}.
- URLs are matched against an allow-list of domains; anything else is rejected with a clear error.
- The extracted id is validated against
idValidatorboth when generating and when redirecting. Manipulated/r/...URLs with malformed ids won't redirect. - Templates only substitute a single
{id}token β there's no arbitrary URL construction. - No central server. The app logic runs entirely in the user's browser. Links you generate and redirects you follow never leave the client.
- The link preview feature uses Microlink.io's public CORS endpoint to fetch Open Graph metadata. Only the reconstructed
webLinkURL is sent to Microlink β no personal data. - The social app preview generator operates via a secure Netlify Edge Function which strictly follows the allowed config routes, acting as an ephemeral proxy strictly just to hydrate OG Meta tags for social crawlers like WhatsApp.
open-deep-redirect/
βββ index.html # the entire app (plus an inline fallback copy of the config)
βββ platforms.json # the only file you need to edit to add platforms
βββ netlify.toml # Netlify config (publish dir, rewrites, edge functions, headers)
βββ _redirects # Netlify rewrite fallback
βββ netlify/
β βββ edge-functions/
β βββ og-injector.js # Deno edge function injecting OG data for Whatsapp/iMessage
βββ scripts/
β βββ sync-inline-config.mjs # embeds platforms.json into index.html
β βββ update-contributors.mjs # regenerates the contributors table in README.md
βββ .github/
β βββ workflows/
β βββ update-contributors.yml # runs update-contributors.mjs on push to main
βββ LICENSE
βββ README.md
Contributions are very welcome! The easiest and most valuable contribution is adding a new platform β it's usually just a few lines of JSON.
- Fork the repo and clone your fork.
- Make your change (see Adding a platform or Editing the config).
- If you edited
platforms.json, keep the inline copy in sync:node scripts/sync-inline-config.mjs
- Test locally with
python3 -m http.server 8080(or any static server) and verify:- Your URL is parsed correctly on the home page and the preview card shows correctly.
- The generated
/r/{platform}/{id}link opens the native app on a phone and falls back to the web.
- Open a pull request describing what platform/content kind you added and include a sample URL.
- Adding more content kinds for existing platforms (e.g. YouTube Shorts, Spotify shows).
- Supporting new platforms (SoundCloud, Snapchat, Bluesky, Mastodon, etc.).
- Improving the UI / accessibility.
- Writing tests for the extractor regexes.
- Adding a
previewHintfield toplatforms.jsonso contributors can declare custom oEmbed or thumbnail URLs per platform.
- Keep the zero-backend, zero-build promise. Any dependency that needs a build step is out of scope.
- Keep the allow-list + regex validation model. Don't add paths that accept arbitrary URLs.
- Don't add tracking, analytics, or third-party scripts that make network calls (Microlink is the sole exception, used only for the optional preview card).
- Match the existing code style β the project uses plain HTML/CSS/JS on purpose.
Open a GitHub issue with:
- Bug: the URL you pasted, what you expected, what happened, and your browser/OS.
- New platform request: a handful of sample URLs for the content kind, plus the app's custom-scheme deep link if you know it.
- The services this project links to β logos, trademarks, and content remain the property of their respective owners. Open Deep Redirect only constructs URLs using public URL schemes; it does not proxy, cache, or serve any content from these services.
- Link preview metadata is fetched via Microlink.io (open-graph scraping) and platform oEmbed endpoints. No content is cached or stored by this project.
- Inspired by the long-standing frustration of sharing a Spotify / YouTube / Instagram link and having it open in the wrong place on someone else's phone.
Released under the MIT License. You are free to use, modify, self-host, and redistribute this project β just keep the copyright notice.
Thanks to everyone who has contributed to this project! π
The table below is auto-generated by scripts/update-contributors.mjs and kept in sync by a GitHub Actions workflow that runs on every push to main.
To regenerate it locally:
node scripts/update-contributors.mjs|
mithun50 π¨ 23 commits |
chandansgowda π¨ 7 commits |
Want to see your avatar here? Contribute a platform or fix and open a pull request!
If you find this useful, consider β starring the repo β it helps other people discover it.