Deep Links and Universal Links with Expo Router
Deep linking in React Native with Expo Router: URL schemes, iOS universal links, Android App Links, testing, deferred links after Firebase Dynamic Links.
Full-Stack, Mobile & AI Engineer
A link in an email, a message from a friend, a notification, a QR code on a poster: every one of them should open the right screen in your app, or a sensible web page if the app isn't installed. When deep linking breaks, it fails quietly. The link opens the home screen, or Safari, or nothing at all, and nobody reports it because they assume it was their mistake.
This guide covers React Native deep linking with Expo Router in 2026: custom URL schemes, iOS universal links, Android App Links, how to test each, why links fail, and what to use for deferred deep links now that Firebase Dynamic Links has shut down.
Key takeaways
- Expo Router makes every route deep-linkable automatically: the file
app/class/[id].tsxanswers/class/123. - Custom schemes (
yourapp://) are easy but can be claimed by other apps; universal links and App Links (https://yourdomain.com/...) are verified and preferred. - Universal links need an
apple-app-site-associationfile on your domain and the associated domains entitlement in the app. - Android App Links need an
assetlinks.jsonfile with your app's signing certificate fingerprint, using the Play App Signing key for store builds. - Firebase Dynamic Links shut down on 25 August 2025. For deferred links (after install), use an attribution vendor; for installed apps, universal links and App Links are enough.
The three kinds of links
| Kind | Example | Verified? | Opens app if installed | If not installed |
|---|---|---|---|---|
| Custom scheme | yourapp://class/123 | No | Yes | Nothing (error) |
| iOS universal link | https://yourdomain.com/class/123 | Yes (domain file) | Yes | Opens the website |
| Android App Link | https://yourdomain.com/class/123 | Yes (domain file) | Yes | Opens the website |
Use HTTPS links wherever users see links (emails, messages, social), and keep the custom scheme for internal flows such as returning from a payment page.
How Expo Router handles links
Expo Router maps paths to files, so you rarely configure linking by hand:
- Set a
schemein your app config ("scheme": "yourapp"). - Any route file becomes a link target:
app/class/[id].tsxhandlesyourapp://class/123and, once universal links are set up,https://yourdomain.com/class/123. - Read parameters with
useLocalSearchParams()and validate them, because links come from outside your app:
// app/class/[id].tsx handles https://yourdomain.com/class/123?ref=email
const { id, ref } = useLocalSearchParams<{ id: string; ref?: string }>();Path segments and query-string values both arrive in the same object, always as strings.
For routes that require sign-in, protect them in the layout so a deep link from a logged-out user goes to sign-in first and then continues to the destination.
Setting up iOS universal links
- Host
apple-app-site-associationathttps://yourdomain.com/.well-known/apple-app-site-association, served as JSON without redirects, listing your Team ID, bundle ID and the paths the app should handle. - Add the associated domains entitlement in your app config, for example
"ios": { "associatedDomains": ["applinks:yourdomain.com"] }, and rebuild. - Install a fresh build, because iOS fetches the association file when the app is installed or updated.
Setting up Android App Links
- Add an intent filter with
autoVerifyfor your domain in the app config'sandroid.intentFilters. - Host
assetlinks.jsonathttps://yourdomain.com/.well-known/assetlinks.jsonwith your package name and SHA-256 certificate fingerprints. - Use the right fingerprint: for apps from Google Play with Play App Signing, include the app signing key's fingerprint from Play Console, plus your upload key's fingerprint for test builds.
From the field: Deep linking is part of the core mobile work on the apps I ship, alongside push notifications and payments, where links bring users back from notifications, emails and payment pages. When a link breaks, the first things I'd check are the domain verification files and certificate fingerprints, because that's where most problems hide, rather than in the app's own code.
Testing deep links
- iOS simulator:
xcrun simctl openurl booted "https://yourdomain.com/class/123" - Android emulator or device:
adb shell am start -W -a android.intent.action.VIEW -d "https://yourdomain.com/class/123" com.yourcompany.app - Custom schemes in Expo:
npx uri-scheme open "yourapp://class/123" --ios(or--android) - Real-world paths: tap links from Notes, Messages, Mail and a messaging app, because some apps open links in in-app browsers that behave differently.
Why deep links don't work
| Symptom | Likely cause |
|---|---|
| iOS opens Safari instead of the app | Association file missing, redirected, wrong Team ID or path, or the app wasn't reinstalled |
| Android shows a chooser or opens the browser | assetlinks.json fingerprint doesn't match the installed build's signing key |
| Works with the scheme, not with HTTPS | Universal links or App Links not configured or not verified |
| Opens the app on the home screen | The route doesn't exist, or params fail validation |
| Works when the app is open, not from cold start | Initial URL not handled before navigation is ready |
iOS universal links: the less obvious causes
If the table above doesn't explain it, iOS has a few behaviours that look like bugs:
- Typing or pasting the URL into Safari's address bar never opens the app. Test by tapping a link in Notes or Messages instead.
- Links within the same domain stay in Safari. Tapping
yourdomain.com/class/123on a page already atyourdomain.comopens it in the browser. - Apple caches your association file through its CDN, so changes can take a while to reach devices. During development, add
?mode=developerto the associated domain (applinks:yourdomain.com?mode=developer) and enable associated-domains development in the device's developer settings to fetch it directly. - A user who long-pressed and chose "Open in Safari" has told iOS to prefer the browser for that domain; the banner at the top of the page can switch it back.
In bare React Native apps without Expo Router, the same files and entitlements apply, and React Navigation's linking configuration maps URLs to screens. Attribution services such as Branch or AppsFlyer add deferred links and analytics on top.
Deferred deep links after Firebase Dynamic Links
A deferred deep link remembers where the user was headed when they install the app from the link, and opens that screen on first launch, which is important for invites and campaigns. Firebase Dynamic Links used to provide this, but it shut down on 25 August 2025. Google's guidance is to use universal links and App Links for installed apps, and a third-party provider (such as Branch, AppsFlyer, Adjust or Airbridge) when you need deferred links or attribution.
FAQ
How do deep links work in Expo Router?
Every file in the app directory is a route with a path, so links like yourapp://class/123 or https://yourdomain.com/class/123 open the matching screen automatically once you've set a scheme and configured universal links or App Links. Read parameters with useLocalSearchParams and validate them.
Why is my iOS universal link opening Safari?
Usually because the apple-app-site-association file isn't reachable at /.well-known/ without redirects, lists the wrong Team ID, bundle ID or paths, or the app wasn't reinstalled after the file changed. Check the associated domains entitlement is in the build as well.
What replaced Firebase Dynamic Links?
Firebase Dynamic Links shut down on 25 August 2025. For apps already installed, use iOS universal links and Android App Links. For deferred deep links and attribution after install, Google suggests third-party providers such as Branch, AppsFlyer, Adjust or Airbridge.
Do I need both a custom scheme and universal links?
Usually yes. Universal links and App Links are best for links people share and see, because they're verified and fall back to your website. A custom scheme is useful for internal flows, such as returning to the app from a payment or authentication page.
Next steps
If your app's links land on the wrong screen, open the browser instead of the app, or you need deferred links for invites, I can set up and test the whole flow. Get in touch, or see how I work as a React Native developer.
Sources
- Linking into your app (Expo), Expo
- iOS universal links (Expo), Expo
- Android App Links (Expo), Expo
- Dynamic Links deprecation FAQ, Firebase
- Verify Android App Links, Android Developers
Related
Push Notifications with Expo: From Setup to Debugging Delivery
React Native push notifications with Expo: credentials, permissions, Android channels, tokens, sending, taps and debugging notifications that never arrive.
Background Tasks in React Native: What Actually Runs
What really runs in the background in React Native apps in 2026: iOS and Android limits, expo-background-task, location and BLE tasks, silent push.
HealthKit and Health Connect in React Native
Integrate Apple HealthKit and Android Health Connect in React Native: libraries, permissions, reading and writing data, privacy rules and Google Fit's end.
