CORE JSC

International Technology Partnership

React Native

Fixing React Native iOS Universal Links That Open Safari Instead of the App

Tapping a shared link from Messages or Mail is supposed to launch straight into the app at the right screen. Instead, Safari opens and loads the web page — sometimes for every link, sometimes only for links tapped from specific apps, sometimes only after a reinstall. Universal Links don't fail with an error; they just silently fall back to being regular web links the moment Apple's own verification doesn't go through.

Core JSC Team·October 2, 2026
React NativeiOSUniversal LinksDeep LinkingDeveloper Tools

The Problem

A React Native iOS app is configured to handle Universal Links — tapping a link to https://example.com/products/123 from Messages, Mail, or another app is supposed to open the app directly to the product screen rather than a browser. In practice, the link opens in Safari instead, loading the actual web page. This can happen for every link, only for links tapped in certain contexts, or only on some devices — and critically, there's no error anywhere: iOS's Universal Links mechanism is designed to fail silently back to a normal web link rather than surface any indication of what went wrong.

Why It Happens

Universal Links depend entirely on Apple being able to fetch and validate a specific file from the domain's server

iOS verifies Universal Links by fetching https://example.com/.well-known/apple-app-site-association (AASA) directly from Apple's own servers — not the device — and checking that it correctly lists the app's bundle identifier and Team ID. If that file is missing, returns the wrong content type, isn't valid JSON, or doesn't list the correct app identifiers, iOS silently treats every link on that domain as a regular web link, with no error visible to the developer or user.

The AASA file has to be served without a redirect and with the correct content type

Apple's fetcher for the AASA file doesn't follow redirects the way a browser does — if the domain redirects HTTP to HTTPS, or redirects between www and non-www, and the AASA file is only reachable after that redirect, Apple's verification fails even though a human clicking the same URL in a browser would be redirected successfully and never notice a problem.

iOS caches the verification result and doesn't always re-fetch it when the app or the file changes

Once iOS has verified (or failed to verify) a domain's association file, it doesn't necessarily re-check on every launch. An app that fixes its AASA configuration after an initial failed verification can continue failing on devices that already cached the earlier negative result, until a full reinstall or, on some iOS versions, a specific re-verification trigger occurs — which makes "it works on a fresh install but not on upgrade" a genuinely common and confusing symptom.

The entitlements and associated domains configuration in the native project have to exactly match what's actually served

The app's own Associated Domains capability (in Xcode, or the equivalent entitlements file) needs an applinks:example.com entry matching the actual domain serving the AASA file, with the correct Team ID prefix. A mismatch here — a typo, a missing subdomain, or a Team ID that doesn't match the one actually in the provisioning profile — causes the same silent fallback even if the server-side AASA file itself is perfectly correct.

The Fix

1. Verify the AASA file is reachable, correctly typed, and not redirected

curl -sI https://example.com/.well-known/apple-app-site-association
# Must return 200 directly, with no redirect, and ideally
# Content-Type: application/json (though Apple tolerates some variance here)
curl -s https://example.com/.well-known/apple-app-site-association | jq .

Fetching the AASA file directly and confirming it returns successfully without a redirect, and that its contents parse as valid JSON with the expected structure, directly verifies the one thing Apple's own fetcher needs — catching server misconfiguration before assuming the problem is in the app's native code.

2. Confirm the AASA file's content matches the app's actual bundle ID and Team ID exactly

{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appID": "ABCDE12345.com.company.appname",
        "paths": ["/products/*", "/orders/*"]
      }
    ]
  }
}

The appID has to be the Team ID (found in Apple Developer account settings) followed by the exact bundle identifier, and the paths array needs to actually include the specific URL patterns the app should handle — a correct-looking file with a typo'd Team ID or a missing path pattern fails verification just as silently as a missing file entirely.

3. Confirm the Associated Domains entitlement in Xcode matches the serving domain exactly

<!-- ios/AppName/AppName.entitlements -->
<key>com.apple.developer.associated-domains</key>
<array>
  <string>applinks:example.com</string>
</array>

The domain in this entitlement needs to match exactly what serves the AASA file — including whether it's www.example.com versus example.com — and the build needs to actually use this entitlements file and a provisioning profile with a matching Team ID, both of which are worth double-checking directly rather than assuming the Xcode project configuration is correct.

4. Force re-verification by fully deleting and reinstalling the app during testing, not just rebuilding

# Fully remove the app from the test device/simulator first
xcrun simctl uninstall booted com.company.appname
npx react-native run-ios

Because iOS caches a previously failed verification, testing a fix by simply rebuilding and reinstalling over the existing app may not actually trigger re-verification — a full uninstall before reinstalling forces iOS to re-fetch and re-evaluate the AASA file fresh, which is necessary to confirm a fix genuinely worked rather than appearing to still fail due to a stale cached result.

Why This Works

Each fix targets a different link in the chain Universal Link verification depends on. Confirming the AASA file is reachable without a redirect addresses the exact constraint Apple's fetcher enforces; verifying the file's content matches the app's real identifiers catches configuration mismatches that look correct at a glance; confirming the Xcode entitlement matches the serving domain closes the gap between server-side and app-side configuration; and forcing a clean reinstall during testing removes the stale-cache variable that otherwise makes it hard to tell whether a fix actually worked.

Conclusion

A Universal Link falling back to Safari isn't a bug in the app's link-handling code — it's iOS's verification against the domain's AASA file failing somewhere, silently, with the fallback to a normal web link being the deliberately quiet failure mode. Verify the AASA file is served directly without a redirect and with correct content, confirm it lists the exact bundle ID and Team ID the app actually uses, confirm the Xcode Associated Domains entitlement matches the serving domain precisely, and test with a full uninstall-reinstall cycle so a stale verification cache doesn't mask whether the actual fix worked.