Deep linking and universal links are the gateways into your application. Deep linking is already part of a seamless experience that any mobile application should have.
Ultimately, they help to reduce churn and increase loyalty of your user base. Implementing them correctly will have a direct impact on your ability to master campaigns and run promotions within your application.
The deep linking question is as important today as ever before, specifically taking into consideration Identifier for Advertisers (IDFA) and a rising number of walled gardens. Well-executed deep linking will enable your retargeting campaigns and bring engagement to a new level, allowing end users to have seamless one-click experience between the web and your application.
Once users discover your application and install it, deep linking becomes a perfect tool to retain newly acquired users in your app.
In this article, I outline existing ways on how to implement deep linking and how to test it using React Native Typescript codebase.
You can find the full source code for this project available on GitHub.
Deep linking, in a nutshell, is a way to redirect users from a webpage into your application in order to show a specific screen with a requested content. It can be a product, an article, secure content behind a paywall, or a login screen.
One of the most famous examples is the link to Slack that they send to your email, which gets opened right inside the application, authorizing you to use it with your account — no password needed.
Deep linking is paramount in 2021. Every effort to lead your users into the app and improve their engagement will heavily depend on the strategy based on top of the deep linking.
To summarize the main points why deep linking is important:
Implementing deep linking requires a more intricate understanding of iOS and Android for extra configuration of each platform in your React Native project.
Take, for example, this syntax diagram of the following URL:
billing-app://billing/1
Whenever you navigate to a website using, for example, https://reactivelions.com, you use a URL in which the URL scheme is “https”. In the example above, billing-app
is a URL scheme for your deep linking URL.
Starting with iOS 9, Apple introduced universal links to reduce confusion and simplify the user experience.
The idea behind using universal links is to connect specific website URLs that match content on your website with content inside your application. This URL would act the same way as the deep linking URL I have shown in the previous section:
https://app.reactivelions.com/billing/3
Configuring universal links requires extra steps on both the server side and mobile side.
First you start with the server side, where you need to upload a JSON formatted file that defines association of the website with a mobile application and its specific routes.
Let’s say you run the example.com domain and you want to create an association file. Start by creating a folder or a route in your root domain .well-known
, then add JSON content inside the apple-app-site-association
:
https://example.com/.well-known/apple-app-site-association
Add JSON content to define website associations:
{ "applinks": { "apps": [], "details": [ { "appID": "ABCD1234.com.your.app", "paths": [ "/billing/", "/billing/*"] }, { "appID": "ABCD1234.com.your.app", "paths": [ "*" ] } ] } }
To demonstrate how deep linking works, we are going to build a simple test application. This application will have straightforward navigation between the Home
and Billing
screens using the @react-navigation
component:
npx react-native init BillingApp --template
Open your Xcode workspace:
open BillingApp/ios/BillingApp.xcworkspace
In your Xcode window, select your newly created project in the left pane (in our case it’s BillingApp). Next, select the BillingApp target inside the newly opened left pane of the internal view for the BillingApp.xcodeproj
.
Navigate to the Info section in the top center of that view, then go to the very bottom and click the plus (+) sign under URL Types. Make sure to add billing-id as your new Identifier and specify URL Schemes as billing-app.
By following these steps above, you’ve enabled iOS project configuration to use deep links like billing-app://billing/4
inside your Objective C and JavaScript code later on.
After configuring Xcode, the next step will be focused on React Native. I will start with linking part of the React Native core called LinkingIOS
. You can read more about it in the official documentation here.
Its main goal is to construct a bridge that will enable a JavaScript thread to receive updates from the native part of your application, which you can read more about in the AppDelegate.m
part below.
Go to ios/Podfile and add this line under target:
pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS'
And then make sure to update your pods using this command:
cd ios && pod install
The next step is to enable the main entry points of your application to have control over the callbacks that are being called when the application gets opened via deep linking.
In this case we implement the function openURL
with options and pass its context to RCTLinkingManager
via its native module called RCTLinkingManager
.
#import <React/RCTLinkingManager.h> - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options { return [RCTLinkingManager application:application openURL:url options:options]; }
For the universal links we will need to implement a callback function continueUserActivity
, which will also pass in context of the app and current universal link into the JavaScript context via RCTLinkingManager
.
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler { return [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler]; }
Android deep linking works slightly differently in comparison to iOS. This configuration operates on top of Android Intents, an abstraction of an operation to be performed. Most of the configuration is stored under AndroidManifest.xml and works by actually pointing to which Intent will be opened when the deep link is executed.
Inside your Android manifest android/app/src/main/AndroidManifest.xml
we need to do the following:
Intent
filterView
action and specify two main categories: DEFAULT
and BROWSABLE
billing-app
and defining the main route as billing
This way Android will know that this app has deep linking configured for this route billing-app://billing/*
:
<intent-filter android:label="filter_react_native"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="billing-app" android:host="billing" /> </intent-filter>
In most production-grade applications you’ll end up having multiple screens, and you are most likely to end up using some form of component that implements this navigation for you. However, you can opt-out and use deep linking without navigation context by invoking React Native’s core library via JavaScript by calling Linking
directly.
You can do this inside your React Native code using these two methods:
Linking.addEventListener('url', ({url}) => {})
Linking.getInitialURL()
Use the acquired deep linking URL to show different content, based on the logic of your application.
If you’re using @react-navigation
you can opt-in to configure deep linking using its routing logic.
For this you need to define your prefixes
for both universal linking and deep linking. You will also need to define config
with its screens
, including nested screens if your application has many screens and is very complex.
Here’s an example of how this configuration looks for our application:
import { NavigationContainer } from '@react-navigation/native'; export const config = { screens: { Home: { path: 'home/:id?', parse: { id: (id: String) => `${id}`, }, }, Billing: { path: 'billing/:id?', parse: { id: (id: String) => `${id}`, }, }, }, }; const linking = { prefixes: ['https://app.reactivelions.com', 'billing-app://home'], config, }; function App() { return ( <NavigationContainer linking={linking} fallback={<Text>Loading...</Text>}> {/* content */} </NavigationContainer> ); }
In the code section above, we introduced universal linking and walked through the steps needed to define universal link association on your website’s server end. In Android there’s something similar called Verified Android App Links.
Using Android App Links helps you avoid the confusion of opening deep links with other applications that aren’t yours. Android usually suggests using a browser to open unverified deep links whenever it is unsure if they are App Links (and not deep links).
To enable App Links verification, you will need to change intent declaration in your manifest file like so:
<intent-filter android:autoVerify="true">
To create app-verified links you will need to generate a JSON verification file that will be placed in the same .well-known
folder as in the Xcode section:
keytool -list -v -keystore my-release-key.keystore
This command will generate association with your domain by signing the configuration with your keystore file:
[{ "relation": ["delegate_permission/common.handle_all_urls"], "target": { "namespace": "android_app", "package_name": "com.mycompany.app1", "sha256_cert_fingerprints": ["14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"] } }]
Then place the generated file to your website using this path:
https://www.example.com/.well-known/assetlinks.json
After going through all configurations and implementations, you want to ensure you’ve set everything up correctly and that deep links work on each platform of your choice.
Before you test universal links or Android App Verified Links, make sure that all JSON files are uploaded, available, and up to date for each of your domains. Depending on your web infrastructure, you might even want to refresh your Content Delivery Network (CDN) cache.
A successful deep linking test means that, after opening a deep link in the browser, you are forwarded to your application and you can see the desired screen with the given content.
When you go to the billing screen you can specify a number, and the application will render the same number of emojis with flying dollar banknotes. Our application has Home
and Billing
screens.
If you try to go to the Billing
screen from your Home
screen, it won’t pass any content, and therefore it will not render any emojis.
In your terminal, you can use these commands to test deep linking for each platform. Play around by changing the number at the end of your deep linking URL to see different numbers of emojis.
npx uri-scheme open billing-app://billing/5 --ios
You can also open Safari and enter billing-app://billing/5
in your address bar, then click go.
npx uri-scheme open billing-app://billing/5 --android
You might have noticed that I used TypeScript to write the code for this project. For this project I’ve implemented custom property types that require custom declaration for each screen. Check props.ts to see these type declarations.
As I mentioned earlier, if you’re building a production-grade application, you’re most likely to end up building complex routing and will need nesting routes to be implemented with your navigator library.
Nesting navigation will enable you to decompose each screen to smaller components and have sub routes based on your business logic. Learn more about building nesting routes using @react-navigation
here.
Looking forward to seeing what you build with this!
LogRocket is a React Native monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in your React Native apps.
LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket's product analytics features surface the reasons why users don't complete a particular flow or don't adopt a new feature.
Start proactively monitoring your React Native apps — try LogRocket for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowThe useReducer React Hook is a good alternative to tools like Redux, Recoil, or MobX.
Node.js v22.5.0 introduced a native SQLite module, which is is similar to what other JavaScript runtimes like Deno and Bun already have.
Understanding and supporting pinch, text, and browser zoom significantly enhances the user experience. Let’s explore a few ways to do so.
Playwright is a popular framework for automating and testing web applications across multiple browsers in JavaScript, Python, Java, and C#. […]
One Reply to "Understanding deep linking in React Native"
how you will share the url example if i share to some one billing-app://billing/5 will it open the app??