React Native Deep Linking with Navigation and Dynamic Links

by Didin J. on Oct 28, 2025 React Native Deep Linking with Navigation and Dynamic Links

Learn how to implement deep linking in React Native using React Navigation and Firebase Dynamic Links with TypeScript for seamless app navigation.

Deep linking is a powerful feature in mobile apps that allows users to navigate directly to specific screens or content using a URL. In React Native, deep linking can greatly enhance user experience — especially when integrated with navigation libraries and Firebase Dynamic Links. Whether users open your app from an email, a website, or a shared link, deep linking ensures they land exactly where they need to be.

In this tutorial, we’ll walk you through how to implement deep linking in a React Native app using React Navigation and Firebase Dynamic Links. You’ll learn how to configure URL schemes, handle incoming links, and test deep links across Android and iOS. By the end, you’ll have a fully working example that supports both app-to-app and web-to-app navigation.

What You’ll Learn

In this tutorial, you’ll learn how to:

  • Configure deep linking in a React Native project.

  • Integrate React Navigation to handle in-app routes.

  • Set up Firebase Dynamic Links for smart cross-platform URLs.

  • Test and verify deep links on both Android and iOS devices.

  • Handle link parameters to display specific content or screens dynamically.

By the end of this guide, you’ll have a React Native app capable of opening precise screens directly from links — whether launched from a browser, another app, or a shared message.


Project Setup

Before diving into deep linking, let’s start by setting up a new React Native project and installing the essential dependencies.

Step 1: Create a New React Native Project

We’ll use the React Native Community CLI to create a fresh project. Open your terminal and run:

npx @react-native-community/cli init RNDeepLinkDemo
cd RNDeepLinkDemo

This will create a new React Native app named RNDeepLinkDemo.

Step 2: Install Required Dependencies

We’ll use React Navigation to manage screens and navigation within the app.

Install React Navigation and its core dependencies:

npm install @react-navigation/native
npm install react-native-screens react-native-safe-area-context

Next, install the stack navigator:

npm install @react-navigation/native-stack

Finally, install TypeScript type definitions:

npm install --save-dev @types/react @types/react-native

Step 3: Set Up React Navigation

Open the App.tsx file and replace its content with the following basic navigation setup:

import * as React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { View, Text, Button } from 'react-native';

type RootStackParamList = {
  Home: undefined;
  Details: { id?: number };
};

const Stack = createNativeStackNavigator<RootStackParamList>();

function HomeScreen({ navigation }: { navigation: any }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text style={{ fontSize: 20 }}>Home Screen</Text>
      <Button
        title="Go to Details"
        onPress={() => navigation.navigate('Details', { id: 42 })}
      />
    </View>
  );
}

function DetailsScreen({ route }: { route: any }) {
  const { id } = route.params || {};
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text style={{ fontSize: 20 }}>Details Screen</Text>
      <Text>ID: {id}</Text>
    </View>
  );
}

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Details" component={DetailsScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

Now, run the app to ensure everything works properly:

npm run android
# or
npm run ios

You should see a simple app with two screens — Home and Details — navigable via a button press.

React Native Deep Linking with Navigation and Dynamic Links -  demo 1


Configure Deep Linking in React Navigation (TypeScript)

React Navigation provides built-in support for deep linking, allowing your app to open specific screens when a user taps a link. In this section, we’ll configure deep linking and test it on both Android and iOS.

Step 1: Define a Deep Link Configuration

In your project root, open App.tsx and update it with a linking configuration. This configuration maps URL paths to navigation routes.

Replace your App.tsx file with the following:

import * as React from 'react';
import { NavigationContainer, LinkingOptions } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { View, Text, Button } from 'react-native';

type RootStackParamList = {
  Home: undefined;
  Details: { id?: number };
};

const Stack = createNativeStackNavigator<RootStackParamList>();

const linking: LinkingOptions<RootStackParamList> = {
  prefixes: ['rnlinkdemo://', 'https://rnlinkdemo.page.link'],
  config: {
    screens: {
      Home: 'home',
      Details: 'details/:id?',
    },
  },
};

function HomeScreen({ navigation }: { navigation: any }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text style={{ fontSize: 20 }}>Home Screen</Text>
      <Button
        title="Go to Details"
        onPress={() => navigation.navigate('Details', { id: 42 })}
      />
    </View>
  );
}

function DetailsScreen({ route }: { route: any }) {
  const { id } = route.params || {};
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text style={{ fontSize: 20 }}>Details Screen</Text>
      <Text>ID: {id}</Text>
    </View>
  );
}

export default function App() {
  return (
    <NavigationContainer linking={linking} fallback={<Text>Loading...</Text>}>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Details" component={DetailsScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

In this example:

  • prefixes defines the URL schemes that trigger your app (rnlinkdemo:// for a custom scheme, and https://rnlinkdemo.page.link for Firebase Dynamic Links).

  • config maps paths (e.g., /details/:id) to screens in your navigation stack.

Step 2: Configure Android Deep Links

Open the file android/app/src/main/AndroidManifest.xml and add an intent filter inside the <activity> tag:

<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="rnlinkdemo" android:host="details" />
</intent-filter>

This tells Android to open the app when a link like rnlinkdemo://details/42 is clicked.

Step 3: Configure iOS URL Schemes

  1. In Xcode, open your project.

  2. Select your app target → Info tab.

  3. Expand URL Types, click +, and set:

    • Identifier: rnlinkdemo

    • URL Schemes: rnlinkdemo

Now iOS will recognize URLs starting with rnlinkdemo://.

Step 4: Test Your Deep Links

Run your app on a simulator or device, then test the links:

Android (via terminal):

adb shell am start -W -a android.intent.action.VIEW -d "rnlinkdemo://details/99" com.rnlinkdemo

iOS (via terminal):

xcrun simctl openurl booted rnlinkdemo://details/99

You should see your app open directly on the Details screen, showing ID: 99.

At this stage, your app supports deep linking with custom URL schemes and navigation routing.


Set Up Firebase Dynamic Links

Now that your app supports basic deep linking with custom URL schemes, let’s take it further using Firebase Dynamic Links — a smarter, more flexible approach that works across platforms (iOS, Android, and web). Dynamic Links survive the app installation process and route users directly to the intended screen once the app is opened.

Step 1: Create a Firebase Project

  1. Go to Firebase Console.

  2. Click Add project and create a new one (e.g., RNDeepLinkDemo).

  3. Once created, open your project dashboard.

Step 2: Enable Dynamic Links

  1. In the left sidebar, select Engage → Dynamic Links.

  2. Click Get Started.

  3. Set up a domain for your links — for example:

     
    https://rnlinkdemo.page.link

     

  4. Copy this domain; we’ll use it in both Android and iOS configurations and in your React Navigation prefixes.

Step 3: Add Android App to Firebase

  1. Go to your Firebase project overview.

  2. Click the Android icon to add an app.

  3. Enter your app’s package name (check in android/app/build.gradle, under applicationId, e.g. com.rnlinkdemo).

  4. Download the google-services.json file and move it to your project’s android/app/ folder.

  5. Add the Firebase Gradle dependencies:

Edit android/build.gradle:

buildscript {
    dependencies {
        classpath("com.google.gms:google-services:4.4.2")
    }
}

Edit android/app/build.gradle:

apply plugin: 'com.google.gms.google-services'

Step 4: Add iOS App to Firebase (Optional but Recommended)

  1. In Firebase, click the iOS icon to add an app.

  2. Enter your iOS bundle ID (found in Xcode → Target → General → Identity).

  3. Download the GoogleService-Info.plist file and add it to your iOS project (Xcode → drag into Runner root).

  4. Add the Firebase/Core pod:

cd ios
pod install
cd ..

Step 5: Install Firebase Dynamic Links Package

To handle Dynamic Links inside React Native, install the React Native Firebase Dynamic Links package:

npm install @react-native-firebase/app @react-native-firebase/dynamic-links

Then rebuild your project:

npm run android
# or
npm run ios

Step 6: Handle Dynamic Links in Your App

Open App.tsx and add Firebase Dynamic Link handlers.
This ensures that when the app is opened via a Dynamic Link, the link is parsed and navigation occurs automatically.

Update your App.tsx like so:

import * as React from 'react';
import { useEffect } from 'react';
import { NavigationContainer, LinkingOptions } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { View, Text, Button } from 'react-native';
import dynamicLinks from '@react-native-firebase/dynamic-links';

type RootStackParamList = {
  Home: undefined;
  Details: { id?: number };
};

const Stack = createNativeStackNavigator<RootStackParamList>();

const linking: LinkingOptions<RootStackParamList> = {
  prefixes: ['rnlinkdemo://', 'https://rnlinkdemo.page.link'],
  config: {
    screens: {
      Home: 'home',
      Details: 'details/:id?',
    },
  },
};

function HomeScreen({ navigation }: { navigation: any }) {
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text style={{ fontSize: 20 }}>Home Screen</Text>
      <Button
        title="Go to Details"
        onPress={() => navigation.navigate('Details', { id: 42 })}
      />
    </View>
  );
}

function DetailsScreen({ route }: { route: any }) {
  const { id } = route.params || {};
  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text style={{ fontSize: 20 }}>Details Screen</Text>
      <Text>ID: {id}</Text>
    </View>
  );
}

export default function App() {
  const handleDynamicLink = (link: any) => {
    if (link?.url) {
      const match = link.url.match(/details\/(\d+)/);
      if (match) {
        // Navigate to details screen
        navigationRef.current?.navigate('Details', { id: Number(match[1]) });
      }
    }
  };

  useEffect(() => {
    const unsubscribe = dynamicLinks().onLink(handleDynamicLink);
    dynamicLinks()
      .getInitialLink()
      .then(handleDynamicLink);
    return () => unsubscribe();
  }, []);

  const navigationRef = React.useRef<any>(null);

  return (
    <NavigationContainer ref={navigationRef} linking={linking}>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Details" component={DetailsScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

Now your app listens for Firebase Dynamic Links — even if the app is closed before opening the link.

Step 7: Test a Firebase Dynamic Link

In Firebase Console → Dynamic Links, click New Dynamic Link, and create one:

  • Domain: https://rnlinkdemo.page.link

  • Deep link URL: https://rnlinkdemo.page.link/details/101

  • Android package name: your app package (e.g. com.rnlinkdemo)

  • iOS bundle ID: your iOS bundle

After creation, open the generated short link on your device — it should open your app and navigate to the Details screen showing ID: 101.

At this point, your app fully supports:

  • Custom scheme deep linking (rnlinkdemo://details/42)

  • Firebase Dynamic Links (https://rnlinkdemo.page.link/details/42)

  • Automatic routing to specific screens


Testing Deep Links on Devices and Emulators

Now that you’ve implemented both React Navigation deep linking and Firebase Dynamic Links, it’s time to test your setup across different environments. Testing deep links ensures users are directed to the correct screen no matter how the link is opened — from a browser, email, or social media.

Step 1: Test Custom Deep Links (App URL Scheme)

🟢 Android

You can test custom scheme links using ADB (Android Debug Bridge).
Make sure your emulator or device is running, then execute:

adb shell am start -W -a android.intent.action.VIEW -d "rnlinkdemo://details/42" com.rnlinkdemo

This should open your app and navigate directly to the Details screen, showing ID: 42.

💡 Tip: Replace com.rnlinkdemo with your actual package name if different.

🟣 iOS

Use the Xcode command line tools to simulate a link click:

xcrun simctl openurl booted rnlinkdemo://details/42

Your app should launch (or come to the foreground) and open the Details screen.

Step 2: Test Firebase Dynamic Links

🧩 Generate a Dynamic Link

If you haven’t already, go to Firebase Console → Engage → Dynamic Links and create a new one.

Example:

https://rnlinkdemo.page.link/details/99

Make sure:

  • The deep link URL includes a valid path (/details/99).

  • The Android package name matches your app.

  • The iOS bundle ID is correctly set.

🔗 Android Testing

  1. Copy the generated Dynamic Link.

  2. Paste it into Chrome or send it to your device via email/message.

  3. Tap the link — if the app is installed, it should open directly to the Details screen with ID: 99.
    If not installed, it will first redirect to the Play Store.

You can also test using ADB:

adb shell am start -W -a android.intent.action.VIEW -d "https://rnlinkdemo.page.link/details/99" com.rnlinkdemo

🍎 iOS Testing

  1. Copy and paste the same Firebase Dynamic Link into Safari on your simulator or physical device.

  2. Tap “Open” when prompted.

  3. The app should launch and route directly to the Details screen.

Step 3: Test App in Background and Closed States

Deep links should work whether your app is running, in the background, or completely closed. Test all three states:

  1. Foreground: Tap the link while the app is open.

  2. Background: Minimize the app and tap the link.

  3. Closed: Fully close the app, then open it via a link.

✅ Each case should result in the same behavior — navigation to the Details screen with the correct ID parameter.

Step 4: Debugging Tips

If your deep links don’t open correctly:

  • Double-check the prefixes in your linking config.

  • Verify your intent filter in AndroidManifest.xml.

  • Confirm your Firebase Dynamic Link domain matches what’s configured in your prefixes.

  • Add a console log inside handleDynamicLink in App.tsx to trace incoming URLs:

const handleDynamicLink = (link: any) => {
  console.log('Received link:', link?.url);
  // Handle navigation...
};

Step 5: Bonus — Shareable Links for Testing

You can generate testable links dynamically from your Firebase Console or by using the REST API:

Example generated link:

https://rnlinkdemo.page.link/?link=https://rnlinkdemo.page.link/details/101&apn=com.rnlinkdemo

Share this link via email or message — clicking it should open your app to Details with ID: 101.

At this point, you’ve successfully tested:

  • Custom URL scheme deep links

  • Firebase Dynamic Links

  • Behavior across foreground, background, and closed states

Now that your app can open from a deep link, the next step is to extract and handle parameters from the incoming URLs.
This is useful when your links contain identifiers, like product IDs, article slugs, or user IDs.

For example, a link such as:

rnlinkdemo://details/42

should navigate to the Details screen and display the ID 42.

Step 1: Update the Linking Configuration

Your linking configuration already defines a dynamic segment for Details. Let’s make sure it’s typed and mapped correctly.

In App.tsx, verify your linking setup includes the correct path and parameter:

const linking: LinkingOptions<RootStackParamList> = {
  prefixes: ['rnlinkdemo://', 'https://rnlinkdemo.page.link'],
  config: {
    screens: {
      Home: 'home',
      Details: 'details/:id?',
    },
  },
};

This ensures that any URL matching /details/:id (e.g., /details/101) is passed to the Details route as a parameter.

Step 2: Extract Parameters from React Navigation

Inside your DetailsScreen, React Navigation automatically provides parameters passed via the route — including those parsed from deep links.

Update the DetailsScreen as follows:

import { RouteProp } from '@react-navigation/native';

type DetailsScreenRouteProp = RouteProp<RootStackParamList, 'Details'>;

function DetailsScreen({ route }: { route: DetailsScreenRouteProp }) {
  const { id } = route.params || {};

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Text style={{ fontSize: 20 }}>Details Screen</Text>
      <Text>ID: {id ? id : 'No ID provided'}</Text>
    </View>
  );
}

This approach uses TypeScript types for better reliability and autocompletion support.

Step 3: Parse Parameters from Firebase Dynamic Links

Firebase Dynamic Links often use full URLs (e.g., https://rnlinkdemo.page.link/details/42).
When handling them, we can use regular expressions or URL parsing to extract parameters manually.

Update your handleDynamicLink function in App.tsx:

const handleDynamicLink = (link: any) => {
  if (link?.url) {
    console.log('Received link:', link.url);

    try {
      // Use the WHATWG URL API if available
      let url: any;

      if (typeof URL !== 'undefined') {
        url = new URL(link.url);
      } else {
        // Fallback for React Native environments
        const match = link.url.match(/:\/\/([^/]+)\/([^/]+)\/?(\d+)?/);
        if (match) {
          const [, host, screen, id] = match;
          if (screen === 'details' && id) {
            navigationRef.current?.navigate('Details', { id: Number(id) });
          }
        }
        return;
      }

      // If URL parsing works normally
      const pathSegments = url.pathname?.split('/') ?? [];
      const screen = pathSegments[1];
      const id = pathSegments[2];

      if (screen === 'details' && id) {
        navigationRef.current?.navigate('Details', { id: Number(id) });
      }
    } catch (error) {
      console.warn('Error parsing dynamic link:', error);
    }
  }
};

This ensures that regardless of how the link is formatted, your app correctly interprets it and navigates to the right screen.

Step 4: Add a Loading or Fallback Screen (Optional)

Sometimes a user may open a deep link before your app finishes initializing (especially on cold starts). To handle this gracefully, you can add a fallback or splash screen while waiting for the navigation container to mount.

Example:

<NavigationContainer
  ref={navigationRef}
  linking={linking}
  fallback={<Text>Loading...</Text>}
>

This displays a brief “Loading…” message until navigation is ready.

Step 5: Test with Various Parameter Scenarios

Try opening your app with different link variations to ensure robust handling:

Link Expected Behavior
rnlinkdemo://details/1 Navigates to Details screen with ID 1
https://rnlinkdemo.page.link/details/2 Opens via Firebase Dynamic Link → navigates to ID 2
rnlinkdemo://details Opens Details screen with “No ID provided”
rnlinkdemo://home Opens Home screen

Step 6: Add Defensive Checks

To prevent crashes from malformed URLs, always validate parsed data:

if (screen === 'details' && !isNaN(Number(id))) {
  navigationRef.current?.navigate('Details', { id: Number(id) });
} else {
  console.warn('Invalid deep link:', link.url);
}

At this stage, your app can:

  • Parse and use URL parameters for dynamic content

  • Safely handle malformed or missing data

  • Work seamlessly with both React Navigation deep links and Firebase Dynamic Links


Conclusion and Next Steps

In this tutorial, you learned how to implement deep linking in a React Native app using both React Navigation and Firebase Dynamic Links — enabling users to jump directly into specific screens or content from a shared URL.

You’ve successfully:

  • Created a React Native project with TypeScript and React Navigation

  • Configured custom URL schemes for Android and iOS

  • Integrated Firebase Dynamic Links for smart, cross-platform links

  • Parsed and handled link parameters to open the correct screen dynamically

  • Tested links on real devices, emulators, and various app states (foreground, background, closed)

With these capabilities, your app is now deep-link–ready, enhancing user experience and engagement by allowing seamless content access across platforms.

🚀 Next Steps

If you’d like to take this project further, here are a few enhancements to consider:

  1. Add Route Parameters for More Screens
    Extend deep linking to other routes like /profile/:username or /product/:slug.

  2. Implement Authentication Flow
    Redirect users to a login or signup screen if they open a deep link requiring authentication.

  3. Custom Link Builders
    Create a small utility to generate Firebase Dynamic Links programmatically for sharing.

  4. Analytics and Attribution
    Use Firebase Analytics to track how users interact with your deep links and campaigns.

  5. Universal Links (iOS) and App Links (Android)
    Configure your domain for full universal link support, so users can open your app directly from standard web URLs.

By mastering deep linking, you’ve unlocked one of the most powerful tools in modern mobile app development — connecting users, content, and platforms effortlessly. 🎉

You can get the full source code on our GitHub.

That's just the basics. If you need more deep learning about React Native, you can take the following cheap course:

Thanks!