Easy Ways to Set Up Your Own Deep Linking System

Rohit LokhandeRohit Lokhande
6 min read

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

IOS: apple-app-site-association (AASA)

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>

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>

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.

0
Subscribe to my newsletter

Read articles from Rohit Lokhande directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Rohit Lokhande
Rohit Lokhande