Easy Ways to Set Up Your Own Deep Linking System


What is Deep Linking?
Deep linking is a technique that takes users directly to a specific page or screen of an app. A deep link is simply a regular web URL. When opened in a browser, it displays a webpage, like a specific post. However, in the context of a mobile app, it opens the app instead of the browser.
Let's look at a real-world example. Imagine you're scrolling through Instagram reels in your free time and come across a funny reel you want to share with a friend. You share the reel through a link on another platform, like WhatsApp. That link is a deep link. When your friend opens the link on their mobile app and already has Instagram installed, it will open in the app. If not, it will redirect them to the Play Store to install the app—this is called deferred deep linking. If your friend opens the link in a browser on a laptop or desktop, it will redirect to the same reel in the browser.
Now that you understand what deep linking is, we will move on to the implementation part. Before we start, let's discuss what we will use in this process.
Tech Used
React Native : We have used this to build mobile app you can use other tech as well.
Aws Ec2 : Cloud server hosting Nginx and your domain
Nginx: Web server to serve Android, IOS configuration file
Node js: Running Server with redirecting web page if application not available on mobile phone you can use any other backend tech as well instead of this.
Now that we understand what we are using and the alternatives, let's start the implementation.
First, we will begin with the backend implementation.
Creating an API Endpoint in Node.js
app.ts
Add below router in app.ts file
...OtherCode
app.use("/s", deeplinkRoutes);
routes/deep-linking.routes.ts
Setup deep linking routes
import { Router } from "express";
import DeeplinkController from "../controllers/deeplink.controller";
const deeplinkRoutes = Router();
deeplinkRoutes.get("/:id", DeeplinkController.deeplink);
export default deeplinkRoutes;
controllers/deeplink.routes.ts
Create controller for deeplink
import { Request, Response } from "express";
class DeeplinkController {
static async deeplink(
req: Request<{ id: string }>,
res: Response
) {
const { id } = req.params;
// Serve HTML page instead of JSON
const htmlContent = `<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>TITLE</title>
<script>
// Try to open the app
setTimeout(function() {
window.location.href = "<APP_NAME>://s/${id}";
}, 50);
// Check if iOS
var userAgent = navigator.userAgent || navigator.vendor || window.opera;
if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {
// For iOS, redirect to App Store
setTimeout(function() {
window.location.href = <APP_STORE_LINK>;
}, 2000);
} else {
// For Android, redirect to Play Store
setTimeout(function() {
window.location.href = <PLAY_STORE_LINK>;
}, 2000);
}
</script>
</head>
<body>
<div style="text-align: center; padding-top: 100px;">
<h1>Opening App...</h1>
<p>If the app doesn't open automatically, <a href="<PLAY_STORE_LINK>">download it from the Play Store</a> or <a href="<APP_STORE_LINK>">App Store</a>.</p>
</div>
</body>
</html>`;
res.setHeader("Content-Type", "text/html");
res.status(200).send(htmlContent);
return;
}
}
export default DeeplinkController;
This endpoint will handle redirection to the Play Store or App Store if the app is not installed on the device.
Android: assetlinks.json
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.yourapp",
"sha256_cert_fingerprints": ["YOUR_SHA256_CERT_FINGERPRINT"]
}
}
]
sha256_cert_fingerprints is used for app security. Get it using:
keytool -list -v -keystore my-release-key.keystore -alias my-key-alias
IOS: apple-app-site-association
{
"applinks": {
"apps": [],
"details": [
{
"appID": "TEAM_ID.com.yourapp",
"paths": []
}
]
}
}
These two files are needed for App Links and Universal Links to verify that your app has permission to open specific links.
Without these rules, when a user clicks a link like “https://myapp.com/s/:id”, it will always open in the browser instead of the app.
Why are these Files Needed?
Android: assetlinks.json
Android needs this file to confirm that the domain (myapp.com) can open links in the app.
It stops malicious apps from pretending to own other domains.
It must be available at:
IOS: apple-app-site-association (AASA)
IOS uses this file to link your domain (my app.com) with your app.
Without this file, Universal Links will not open your app.
It must be hosted at:
How Do These Files Work?
A user clicks a deep link (https://myapp.com/s/asf313ehvas56dshtfoisag).
The OS checks if the app is installed:
If installed → It looks for the assetlinks.json (Android) or AASA (iOS) file.
If verified → The app opens.
If verification fails or the app is not installed → The link opens in a web browser.
File Verification Steps:
Android/iOS checks https://myapp.com/.well-known/ for the file.
If the app’s package name (com.yourapp) or App ID matches, the OS allows deep linking.
Now that we understand the role of these two files, we will serve them through Nginx on an EC2 instance.Step 1: Prepare the apple-app-site-association file and assetlinks.json file
First, log in to the EC2 instance using SSH, then follow these steps:
Create the .well-known Directory
Run the following command on your EC2 instance:
sudo mkdir -p /var/www/html/.well-known
Now create assetlinks.json and apple-app-site-association in that path.
Set Correct permissions
sudo chmod 644 /var/www/html/.well-known/apple-app-site-association
sudo chown www-data:www-data /var/www/html/.well-known/apple-app-site-association
sudo chmod 644 /var/www/html/.well-known/assetlinks.json
sudo chown www-data:www-data /var/www/html/.well-known/assetlinks.json
Step 2: Configure Nginx to Serve the file
Go to nginx path
cd /etc/nginx
Create conf.d and application conf file if not available
ubuntu@EC2:/etc/nginx$ mkdir conf.d && cd conf.d
ubuntu@EC2:/etc/nginx/conf.d$ sudo vi app_name.conf
Find the server {} block and add this inside it:
location /.well-known/assetlinks.json {
add_header Content-Type application/json;
root /var/www/html;
}
Step 3: Restart Nginx
sudo systemctl restart nginx
Check if Nginx is running:
sudo systemctl status nginx
Step 4: Verify the File is Accessible
Now, visit:
https://mydomain.com/.well-known/apple-app-site-association
https://mydomain.com/.well-known/assetlinks.json
Note: Do add a SSL certificate
With this we are done with backend implementation now let’s implement on mobile app side.
Mobile Application Setup
Step 1: React Native Setup
Install Dependencies
npm install @react-navigation/native react-native-screens react-native-safe-area-context react-native-gesture-handler
npm install react-native-reanimated
Configure Navigation with Deep Linking
const linking = {
prefixes: ['https://mydomain.com','<app_name>://'], // Also added custome schema for redirection from browser
config: {
screens: {
Home: '',
Profile: 's/:id',
},
},
};
<NavigationContainer linking={linking}>
{/* Your Stack Navigator */}
</NavigationContainer>
Step 2: Android App Links Integration
Add Intent Filter to AndroidManifest.xml
inside<activity android:name=”.MainActivity:>:
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="<APP_NAME>" android:host="mydomain.com" />
</intent-filter>
Step 3: Universal Links Integration
Entroll in Apple Developer Program
You must enroll to get your Team ID and use Associated Domains.
Enable Associated Domains in Xcode
Open your iOS project in Xcode.
Select your app target.
Go to the “Signing & Capabilities” tab.
Click the “+ Capability” button.
Select “Associated Domains” from the list.
In the new “Associated Domains” section, click the + and add:
applinks:mydomain.com
That's it! Congratulations, you have successfully created a custom deep linking implementation. Now, if you open https://mydomain.com/s/f15f8008-c9ab-49b2-a995-4c8f488df2f6, it will open directly in the app.
Conclusion
Implementing deep linking in a React Native app using your own domain—without relying on Firebase—gives you complete control over link behavior, branding, and user experience. By integrating Android App Links and iOS Universal Links, and properly setting up domain verification through assetlinks.json and apple-app-site-association, your app can open specific screens directly from links shared via WhatsApp, email, or anywhere else.
Setting up custom deep linking requires some initial backend work (like SSL, hosting .well-known files, and domain verification), but it’s a future-proof and production-ready approach that works across platforms. Once everything is set up correctly, users with your app installed will enjoy seamless navigation, and those without it will be smoothly redirected to the App Store or Play Store.
If you’re building a serious mobile product, choosing this method ensures your linking experience is secure, fast, and completely under your control—with no third-party dependency.
Subscribe to my newsletter
Read articles from Rohit Lokhande directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
