Skip to main content
Engineering6 min readSeptember 2, 2025

Deep Linking in Mobile Apps: Implementation Guide

A practical guide to implementing deep links in mobile apps — URI schemes, universal links, app links, deferred deep linking, and handling edge cases.

James Ross Jr.
James Ross Jr.

Strategic Systems Architect & Enterprise Software Developer

Deep linking lets users tap a link and land directly on a specific screen in your app instead of the home screen. It sounds simple, but the implementation involves coordinating between your app, your website, the mobile operating system, and the app stores. Getting it right dramatically improves user experience and marketing effectiveness.

Here is the practical guide to implementing deep links that work reliably.

There are three distinct types, and you probably need all of them.

URI scheme links use a custom protocol like myapp://profile/123. These are the simplest to implement — register a scheme in your app configuration, and the OS routes matching URLs to your app. The downside is that URI schemes only work when the app is installed. If the app is not installed, the link fails silently or shows an error. They are also not unique — any app can register any scheme, creating potential conflicts.

Universal Links (iOS) and App Links (Android) use standard HTTPS URLs like https://myapp.com/profile/123. When the app is installed, the OS intercepts the URL and opens the app. When the app is not installed, the URL opens in the browser as a normal web page. This requires hosting verification files on your web domain — apple-app-site-association on iOS and assetlinks.json on Android — that prove you own both the domain and the app.

Deferred deep links handle the case where a user taps a link but does not have the app installed yet. The link sends them to the app store, they install the app, and on first launch, the app routes them to the original destination. This requires a service (Branch, Firebase Dynamic Links, or a custom solution) that persists the link context through the install flow.

For production apps, I implement all three. Universal links and app links for the primary experience, URI schemes as a fallback for in-app routing, and deferred deep links for marketing and sharing flows where the user might not have the app yet.

Implementation Details

On iOS, universal links require an apple-app-site-association (AASA) file hosted at your domain root or .well-known directory. This JSON file maps URL paths to your app's bundle ID. The file must be served over HTTPS without redirects, with the correct content type.

{
 "applinks": {
 "apps": [],
 "details": [{
 "appID": "TEAMID.com.yourcompany.yourapp",
 "paths": ["/profile/*", "/product/*", "/share/*"]
 }]
 }
}

On Android, app links require a assetlinks.json file at https://yourdomain.com/.well-known/assetlinks.json that maps your domain to your app's package name and signing certificate fingerprint. You also need to declare intent filters in your Android manifest with autoVerify="true".

In Expo, both configurations are handled through app.config.ts with the associatedDomains property for iOS and intentFilters for Android. Expo Router then handles the routing — incoming deep links are matched against your file-based routes automatically. This is one of the strongest arguments for using Expo in production.

Handling Edge Cases

Deep links interact with authentication, navigation state, and app lifecycle in ways that create edge cases you need to handle deliberately.

Authenticated routes. When a deep link points to a screen that requires authentication (a user profile, an order detail), and the user is not logged in, you need to redirect through the login flow and then continue to the original destination. Store the intended deep link target, present the login screen, and on successful authentication, navigate to the stored target. Do not just drop the deep link — users expected to see that content.

Navigation stack construction. When a user deep links to product/123, what happens when they tap the back button? They should navigate to the product list, not exit the app. Construct a logical navigation stack based on the deep link path. If the link goes to /orders/456/item/789, the stack should include the orders list, order 456, and item 789.

App already running. Deep links behave differently depending on whether the app is cold-started, launched from background, or already in the foreground. Test all three scenarios. In React Native, the Linking API provides different hooks for initial URL (cold start) and URL changes (warm start). Expo Router handles both cases, but verify that your navigation state updates correctly in each scenario.

Link expiration and invalid targets. Deep links shared months ago may point to content that no longer exists. A shared product link for an item that has been removed, or a shared profile link for a deleted account, should show a meaningful error rather than a blank screen or a crash. Validate the deep link target and show a helpful fallback.

Testing and Debugging

Deep linking is notoriously difficult to test because it involves system-level URL handling that differs between development and production builds.

On iOS, test universal links by sending them via iMessage or Notes — tapping links in Safari does not trigger universal links by design. Use the developer console to verify AASA file validation. On Android, use adb shell am start to send intent URLs directly to the device.

In development, Expo provides npx uri-scheme to test URI scheme links and npx expo start --dev-client to test universal links in development builds. Always test deep links in production-signed builds before release — the verification process for universal links and app links only works with production signing.

Set up automated tests that verify deep link routing. In your testing strategy, include test cases for each deep link pattern that verify the correct screen renders with the correct data. Deep link regressions are easy to introduce and hard to notice without automated coverage.

Monitor deep link performance in production using your analytics setup. Track which deep links are tapped, which successfully navigate to the intended screen, and which fail. High failure rates on specific link patterns indicate configuration issues or content availability problems that need investigation.