Why Deep Linking Matters for React Native Apps
Deep linking allows users to jump directly to specific content in your app from external sources—emails, SMS, web pages, ads, and social media. Instead of landing on the app home screen, users land exactly where you want them, dramatically improving user experience and conversion rates.
Deferred deep linking takes this further: links work even if the app isn't installed yet. When a user clicks a link, we detect the install, and after the app opens for the first time, the SDK retrieves the original link data and routes the user to the correct destination. This is crucial for acquisition campaigns, app launch promotions, and referral programs.
The React Native Redirectly SDK (react-native-redirectly) is a pure TypeScript implementation with full type definitions. It works seamlessly with both bare React Native and Expo, and handles the entire deep linking flow—no native code changes required for the SDK itself.
Deferred deep linking
Links survive app install and route users to the right content.
Install attribution
Match installs to link clicks and campaigns.
TypeScript
Full type definitions, works with Expo and bare RN.
Before you start
This guide assumes you have a React Native project set up and running. Here's what you need:
- React Native 0.61+ (0.70+ recommended)
- React Navigation v5+ (v6 recommended for best integration)
- Redirectly account and API key (sign up free at redirectly.app)
- Redirectly subdomain (e.g., myapp.redirectly.app)
- For bare RN: Xcode (iOS) and Android Studio (Android)
- For Expo: EAS Build (optional, for production deep linking)
- AsyncStorage (optional but recommended for install attribution persistence)
This guide covers both Expo and bare React Native. Some sections apply only to bare RN (iOS Universal Links, Android App Links). Expo users can skip directly to the Expo Configuration section.
iOS Universal Links Setup
iOS uses Universal Links to route deep links to your app. Universal Links require configuration in Xcode, an entitlements file, and an AASA (apple-app-site-association) file hosted on your domain. Redirectly automatically hosts the AASA file for your subdomain.
Step 1a: Xcode Associated Domains Configuration
Open your project in Xcode, select the target, and go to Signing & Capabilities. Add the Associated Domains capability (+ Capability button).
Associated Domains Capability
applinks:myapp.redirectly.app webcredentials:myapp.redirectly.app
Replace myapp with your Redirectly subdomain.
Step 1b: Entitlements File
The associated domains must also be declared in your entitlements file. Xcode usually manages this, but you can verify or manually add to your {ProjectName}.entitlements file:
{ProjectName}.entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:myapp.redirectly.app</string>
<string>webcredentials:myapp.redirectly.app</string>
</array>
</dict>
</plist>Replace myapp with your Redirectly subdomain.
Step 1c: AppDelegate Configuration (Optional)
If you're using React Navigation, most handling is done in the Navigation linking config (see React Navigation section below). However, you may need to handle the app delegate method for certain scenarios:
AppDelegate.swift (Optional)
import UIKit
import React
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
// React Navigation and Redirectly SDK handle this via linking config
return true
}
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
// Handle universal links - React Navigation handles this
return RCTLinkingManager.application(application, continue: userActivity, restorationHandler: restorationHandler)
}
// ... rest of AppDelegate
}Step 1d: AASA Verification
Redirectly automatically hosts the apple-app-site-association file at https://myapp.redirectly.app/.well-known/apple-app-site-association. Verify it's correct and properly signed using our AASA validator tool.
Android App Links Setup
Android uses App Links to route deep links to your app. Unlike Universal Links, App Links require digital asset verification via an assetlinks.json file. Redirectly hosts this file for your subdomain, and you declare the link intent filter in AndroidManifest.xml.
Step 2a: Intent Filter in AndroidManifest.xml
Add an intent filter to your MainActivity in android/app/src/main/AndroidManifest.xml:
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<application>
<activity
android:name=".MainActivity"
android:exported="true">
<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="https"
android:host="myapp.redirectly.app" />
</intent-filter>
</activity>
</application>
</manifest>Replace myapp.redirectly.app with your Redirectly subdomain. The android:autoVerify="true" attribute triggers automatic verification of the assetlinks.json file.
Step 2b: assetlinks.json Verification
Redirectly automatically hosts the assetlinks.json file at https://myapp.redirectly.app/.well-known/assetlinks.json. Verify it's correct using our assetlinks validator tool. You'll need your app's signing certificate SHA256 fingerprint, which you can find in your Android keystore.
Get your SHA256 fingerprint
keytool -list -v -keystore android/app/debug.keystore -alias androiddebugkey -storepass android -keypass android
For release builds, use your release keystore path and credentials.
SDK Installation & Initialization
The react-native-redirectly SDK is pure TypeScript. Install it via npm or yarn, then initialize with your API key at app startup.
Install the SDK
npm or yarn
npm install react-native-redirectly # or yarn add react-native-redirectly
Initialize Redirectly
Initialize the SDK at your app's entry point (App.tsx or index.js):
App.tsx
import React, { useEffect } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { Redirectly } from 'react-native-redirectly';
import AsyncStorage from '@react-native-async-storage/async-storage';
import RootStack from './navigation/RootStack';
export default function App() {
useEffect(() => {
const initializeRedirectly = async () => {
try {
const redirectly = Redirectly.getInstance();
await redirectly.initialize({
apiKey: 'your-api-key',
enableDebugLogging: __DEV__,
asyncStorage: AsyncStorage, // Optional but recommended
});
// Listen for install attribution
redirectly.onAppInstalled((installData) => {
if (installData.matched) {
console.log(
'Install attributed to:',
installData.matchedClick?.slug
);
// Store or send to analytics
}
});
// Listen for incoming deep links
redirectly.onDeepLink((deepLink) => {
console.log('Deep link received:', deepLink);
// Handle navigation - integrate with React Navigation
});
} catch (error) {
console.error('Failed to initialize Redirectly:', error);
}
};
initializeRedirectly();
}, []);
return (
<NavigationContainer>
<RootStack />
</NavigationContainer>
);
}For Expo
The SDK works the same in Expo. Install and initialize identically. For production Expo apps, configure deep linking in your app.json (see the Expo Configuration section below).
Deferred Deep Link Handling
Deferred deep links are links that were clicked before the app was installed. The Redirectly SDK detects the install and delivers the original link data when the app opens for the first time. You listen for this via the onAppInstalled callback.
Handle onAppInstalled Callback
The onAppInstalled callback fires when the app is opened for the first time after installation, and if the install matched a Redirectly link click, it provides the deferred deep link data:
Deferred Link Handling
const redirectly = Redirectly.getInstance();
redirectly.onAppInstalled(async (installData) => {
console.log('Install Data:', installData);
// installData structure:
// {
// matched: boolean,
// matchedClick?: {
// slug: string,
// url: string,
// timestamp: number,
// utmParams?: {
// source?: string,
// medium?: string,
// campaign?: string,
// content?: string,
// term?: string,
// }
// },
// deferred?: {
// url: string,
// customData?: Record<string, any>,
// }
// }
if (installData.matched) {
console.log('Install matched a Redirectly click');
// Record attribution
console.log('Campaign:', installData.matchedClick?.utmParams?.campaign);
console.log('Source:', installData.matchedClick?.utmParams?.source);
// Handle deferred deep link
if (installData.deferred?.url) {
console.log('Deferred Deep Link:', installData.deferred.url);
// Navigate to the screen specified in the deep link
// Parse the URL and navigate
const { path, params } = parseDeepLink(installData.deferred.url);
// For example:
// If deferred URL is "https://myapp.redirectly.app/product/123"
// Navigate to { name: 'Product', params: { productId: '123' } }
}
}
});
function parseDeepLink(url: string) {
// Simple example - your implementation may be more complex
const parsed = new URL(url);
const path = parsed.pathname;
if (path.startsWith('/product/')) {
const productId = path.split('/')[2];
return {
screen: 'Product',
params: { productId },
};
}
if (path.startsWith('/profile/')) {
const userId = path.split('/')[2];
return {
screen: 'Profile',
params: { userId },
};
}
return { screen: 'Home', params: {} };
}Attribution Data
The installData includes UTM parameters and other attribution data. Use this for analytics and campaign tracking:
Attribution Example
// Example: tracking with analytics service
redirectly.onAppInstalled((installData) => {
if (installData.matched && installData.matchedClick) {
const { utmParams, timestamp } = installData.matchedClick;
// Send to your analytics backend
analytics.trackInstall({
source: utmParams?.source,
medium: utmParams?.medium,
campaign: utmParams?.campaign,
timestamp,
});
// Track in segment, mixpanel, firebase, etc.
segment.track('App Installed', {
utm_campaign: utmParams?.campaign,
utm_source: utmParams?.source,
matched: true,
});
}
});Testing & Debugging
Test deep links on simulators, emulators, and real devices using platform-specific tools. Enable debug logging in the SDK to troubleshoot issues.
iOS Testing (Simulator & Device)
Test via xcrun (Simulator)
xcrun simctl openurl booted "https://myapp.redirectly.app/product/123"
Replace myapp with your subdomain.
Test via Safari (Real Device)
Open Safari on your device and navigate to your Redirectly subdomain URL directly, or create an HTML page with a link to test.
Android Testing (Emulator & Device)
Test via adb (Emulator & Device)
adb shell am start -a android.intent.action.VIEW -d "https://myapp.redirectly.app/product/123"
Replace myapp with your subdomain.
View adb logs
adb logcat | grep -i "redirectly" # or filter by tag adb logcat -s "RedirectlySDK"
Enable Debug Logging
Set enableDebugLogging to true when initializing the SDK to see detailed logs:
Debug Mode
const redirectly = Redirectly.getInstance();
await redirectly.initialize({
apiKey: 'your-api-key',
enableDebugLogging: true, // Enable for development
});Common Issues
iOS: Links not opening app
Check Associated Domains in Signing & Capabilities. Verify AASA file using the AASA validator tool.
Android: Links not opening app
Verify intent filter autoVerify attribute is present. Check assetlinks.json with the assetlinks validator.
Deep link data not received
Enable debug logging and check console output. Ensure your linking config matches your URL structure.
Deferred link data missing
Ensure AsyncStorage is configured for install tracking. Uninstall and reinstall the app to test first-open.
Expo Configuration
Redirectly works seamlessly with Expo. The SDK is pure TypeScript, so it works out of the box. For production Expo apps, configure deep linking in your app.json so the Expo build system registers your deep link handler.
Configure app.json
Add the deep linking configuration to your Expo app.json to register your Redirectly subdomain:
app.json
{
"expo": {
"scheme": "myapp",
"plugins": [
[
"expo-build-properties",
{
"ios": {
"useFrameworks": "static"
}
}
]
],
"ios": {
"associatedDomains": [
"applinks:myapp.redirectly.app",
"webcredentials:myapp.redirectly.app"
]
},
"android": {
"intentFilters": [
{
"action": "VIEW",
"autoVerify": true,
"data": [
{
"scheme": "https",
"host": "*.redirectly.app"
}
],
"category": ["BROWSABLE", "DEFAULT"]
}
]
}
}
}Replace myapp with your Redirectly subdomain.
EAS Build for Production
For production Expo apps, use EAS Build to build your app with the correct certificates and entitlements:
Build Command
eas build --platform ios --profile production eas build --platform android --profile production
Expo Go Limitations
Expo Go (the preview app) does not support custom URL schemes or deep linking from your subdomain. For testing deep links in development, use a custom dev client or EAS Build.
Migration from Firebase Dynamic Links
If you're currently using Firebase Dynamic Links (FDL), Redirectly offers a modern, simpler alternative with comparable functionality. The migration involves updating your SDK initialization and link handling code.
API Comparison
Firebase vs Redirectly
// Firebase Dynamic Links
const link = await dynamicLinks().getInitialLink();
if (link) {
handleDeepLink(link.url);
}
dynamicLinks().onLink((link) => {
handleDeepLink(link.url);
});
// Redirectly
const redirectly = Redirectly.getInstance();
await redirectly.initialize({ apiKey: 'your-key' });
redirectly.onAppInstalled((installData) => {
if (installData.deferred?.url) {
handleDeepLink(installData.deferred.url);
}
});For a detailed step-by-step migration guide with code examples and best practices, see our Firebase Dynamic Links alternative page. Also check out the deep linking guide and deferred deep linking concepts.
Frequently Asked Questions
What is deferred deep linking?
Deferred deep linking allows links to work even if the app is not installed. When a user clicks a Redirectly link and doesn't have the app, they're directed to the app store. After installing, the app opens and the SDK retrieves the original link data, routing the user to the correct screen. This is critical for acquisition campaigns.
Does Redirectly work with Expo?
Yes, Redirectly works with both Expo and bare React Native. For Expo, configure deep linking in app.json and use EAS Build for production. The SDK is pure TypeScript and works out of the box.
What is install attribution?
Install attribution matches app installs to specific link clicks or campaigns. When a user installs the app after clicking a Redirectly link, the onAppInstalled callback provides details about which campaign, source, or medium drove the install. You can use this data for analytics and campaign optimization.
Do I need to write native code?
For bare React Native, you need to configure iOS and Android native deep link settings (entitlements, AndroidManifest.xml, AppDelegate). However, the Redirectly SDK itself is pure TypeScript with no native module code. For Expo, you configure deep linking in app.json.
How do I test deep links in the simulator/emulator?
For iOS Simulator, use xcrun simctl openurl. For Android Emulator, use adb shell am start. See the Testing & Debugging section for detailed commands and examples.
Can I migrate from Firebase Dynamic Links?
Yes. Redirectly provides similar functionality with a comparable API. See our Firebase Dynamic Links alternative page for step-by-step migration instructions and code comparisons.
Have questions? Check out the React Native SDK docs or explore related guides:
- AASA Validator Tool — Verify your iOS apple-app-site-association file
- assetlinks Validator Tool — Verify your Android assetlinks.json file
- Flutter Deep Linking Guide — Deep linking for Flutter apps
- Firebase Dynamic Links Alternative — Migration guide and comparison
- Deep Linking Guide — Core deep linking concepts
- Deferred Deep Linking — Detailed deferred linking information