Device Features· 6 min read

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.

By

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].tsx answers /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-association file on your domain and the associated domains entitlement in the app.
  • Android App Links need an assetlinks.json file 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.
KindExampleVerified?Opens app if installedIf not installed
Custom schemeyourapp://class/123NoYesNothing (error)
iOS universal linkhttps://yourdomain.com/class/123Yes (domain file)YesOpens the website
Android App Linkhttps://yourdomain.com/class/123Yes (domain file)YesOpens 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.

Expo Router maps paths to files, so you rarely configure linking by hand:

  • Set a scheme in your app config ("scheme": "yourapp").
  • Any route file becomes a link target: app/class/[id].tsx handles yourapp://class/123 and, 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.

  1. Host apple-app-site-association at https://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.
  2. Add the associated domains entitlement in your app config, for example "ios": { "associatedDomains": ["applinks:yourdomain.com"] }, and rebuild.
  3. Install a fresh build, because iOS fetches the association file when the app is installed or updated.
  1. Add an intent filter with autoVerify for your domain in the app config's android.intentFilters.
  2. Host assetlinks.json at https://yourdomain.com/.well-known/assetlinks.json with your package name and SHA-256 certificate fingerprints.
  3. 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.
  • 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.
SymptomLikely cause
iOS opens Safari instead of the appAssociation file missing, redirected, wrong Team ID or path, or the app wasn't reinstalled
Android shows a chooser or opens the browserassetlinks.json fingerprint doesn't match the installed build's signing key
Works with the scheme, not with HTTPSUniversal links or App Links not configured or not verified
Opens the app on the home screenThe route doesn't exist, or params fail validation
Works when the app is open, not from cold startInitial URL not handled before navigation is ready

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/123 on a page already at yourdomain.com opens 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=developer to 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.

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

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.

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.

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.

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

Bhuwanesh Sisodia

Bhuwanesh Sisodia

Full-Stack, Mobile & AI Engineer

Bhuwanesh Sisodia is a Full-Stack, Mobile & AI Engineer based in Mumbai, with 7+ years of experience and 50+ apps shipped to the App Store and Google Play. He works independently with startups and product teams worldwide.

About meGitHubLinkedInX

Related