PSA: Instagram/TikTok in-app browsers block every client-side path to the App Store. Here's the server-side fix that actually works.

PSA: Instagram/TikTok in-app browsers block every client-side path to the App Store. Here's the server-side fix that actually works.

If you put a "link in bio" that's supposed to send people to the App Store or Play Store, and you've noticed it just… doesn't work from Instagram or TikTok — you're not crazy. I burned a couple days on this so here's the writeup.

The problem

Instagram, TikTok, and Facebook open links in their own in-app webview, not Safari/Chrome. That webview sandboxes a bunch of navigation. On iOS specifically, I tested every client-side route to the App Store and they all dead-end (blank screen, nothing happens):

  • window.location / JS redirect to apps.apple.com
  • a plain <a href> tap
  • universal links
  • itms-apps:// / itms-appss:// schemes

What actually works: a server-side 302

The one thing the in-app webview will honor is the initial navigation being a redirect. If the very first response from your URL is a 302 whose Location is the store URL, the webview hands that off to the native store app. This is the same mechanism Linktree/Branch use under the hood.

So instead of an HTML page with redirect JS, your /get link needs to be a tiny endpoint that returns a 302 based on User-Agent. A Cloudflare Worker (free tier) does it:

const PLAY = "https://play.google.com/store/apps/details?id=YOUR.PACKAGE";
const APPSTORE = "https://apps.apple.com/app/idYOURID";

export default {
  async fetch(request) {
    const ua = request.headers.get("user-agent") || "";
    const store = /android/i.test(ua) ? PLAY
                : /iPhone|iPad|iPod/i.test(ua) ? APPSTORE
                : PLAY; // desktop fallback
    return new Response(null, {
      status: 302,
      headers: { Location: store, "Cache-Control": "no-store" },
    });
  },
};

Two gotchas that cost me time:

  1. iOS + Instagram/TikTok is still special. Even the server 302 (both https and itms-appss) dead-ends inside Instagram's iOS webview specifically. The only reliable route there is the user's own "Open in external browser" menu. So for that one case I don't 302 — I serve a tiny interstitial that shows one instruction: tap ⋮ → Open in external browser. Android in-app browsers and iOS Safari all take the 302 fine; it's just IG/TikTok-on-iOS that needs the manual hop.
  2. Don't let the edge cache your 302. Cloudflare will happily cache a bare 302 (~20 min). If you ever change the redirect logic, stale routing bites you. Cache-Control: no-store on the redirect fixes it.

Bonus: since the redirect is server-side, you can read utm_source off the query string and fold it into Play's &referrer= for install attribution — something you can't reliably do client-side.

Hope this saves someone the debugging. Curious if anyone's found a way around the iOS-in-app-webview limitation that doesn't need the "open in browser" step — that's the one part I couldn't fully automate.

u/AthleteWhoCodes — 3 days ago
▲ 19 r/KotlinMultiplatform+1 crossposts

A single KMP dependency dyld-crashed our iOS app at launch on every device below iOS 26 — and no simulator caught it. Here's the trap.

Sharing a painful one in case it saves someone a bad week.

I build a fitness app in Kotlin Multiplatform + Compose (shared Android/iOS). Everything ran perfectly in dev and on TestFlight. Then a user on iOS 18.x sent a video: instant white-screen death on launch, before anything rendered.

Root cause: a KMP library I'd added referenced HealthKit symbols (HKMedicationGeneralFormCapsule and friends) that only exist in the iOS 26 SDK. Kotlin/Native emits those as strong dyld imports in the statically-linked framework — so on any device below iOS 26, dyld can't resolve the symbol and kills the process before main()Symbol not found. No stack trace in the app, no crash in Crashlytics-style tools, nothing — it dies before your code runs.

Why every test missed it: all my simulators and devices were on iOS 26.x. The bug is completely invisible on new OSes and fatal on old ones. You have to run a min-deployment-target OS to see it (I had to download an 18.5 runtime).

The fix (one line, iOS target's OTHER_LDFLAGS):

-Wl,-weak_framework,HealthKit

Weak-linking makes the missing symbols resolve to null instead of aborting; everything I actually use exists on 18.x, so behavior is unchanged.

Two takeaways I'm now religious about:

  1. Smoke-test every release on your minimum supported OS, not just the latest.
  2. Before shipping, check for new-SDK-only symbols: nm -u <binary> | grep -i <framework>.

Bumping any KMP lib that's compiled against a newer SDK is where this bites. Hope it saves someone the panic.

reddit.com
u/AthleteWhoCodes — 8 days ago