Learn how to implement deep linking in React Native apps using React Navigation v6. Master linking configuration, screen mapping with TypeScript, handling deferred deep links, and testing strategies for both iOS and Android platforms.
Deep linking in React Native allows your app to handle URLs and route them to specific screens. When users click a link from email, SMS, push notifications, or web pages, React Navigation intercepts that URL and navigates to the appropriate screen with the relevant data.
Custom URL schemes like 'myapp://' let you handle app-specific links.
myapp://product/123Works when app is installed
Standard HTTPS URLs with fallback to web when app isn't installed.
https://myapp.com/product/123Seamless iOS & Android experience
React Navigation v6 provides a unified API to handle both URL schemes and universal/app links, making it easy to implement deep linking across iOS and Android with a single codebase.
React Navigation v6 uses a 'linking' prop on the NavigationContainer to define how URLs map to navigation state.
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
const Stack = createNativeStackNavigator();
const linking = {
prefixes: ['myapp://', 'https://myapp.com', 'https://*.myapp.com'],
config: {
screens: {
Home: '/',
Product: 'product/:id',
User: 'user/:userId',
NotFound: '*',
},
},
};
export default function RootNavigator() {
return (
<NavigationContainer linking={linking}>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Product" component={ProductScreen} />
<Stack.Screen name="User" component={UserScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}Prefixes: Define the URL schemes and domains your app responds to. Use wildcard subdomains for branch links or deep link services.
React Navigation maps URL patterns to screens and extracts parameters. The 'config.screens' object defines these mappings using route patterns.
interface RootStackParamList {
Home: undefined;
Product: { id: string };
Cart: { items?: string[] };
Profile: { userId: string };
Settings: { section?: string };
}
const linking = {
prefixes: ['myapp://', 'https://myapp.com'],
config: {
screens: {
// Exact matches
Home: '',
// Dynamic parameters
Product: 'product/:id',
// Multiple parameters
Cart: 'cart/:itemId',
// Optional parameters
Profile: 'user/:userId',
Settings: {
path: 'settings/:section?',
parse: {
section: (section: string) => section?.toLowerCase(),
},
},
// Nested navigators
Account: {
screens: {
Orders: 'account/orders',
Wishlist: 'account/wishlist/:categoryId?',
},
},
// Fallback for unmapped routes
NotFound: '*',
},
},
};myapp://product/123→ Product screen with id='123'
https://myapp.com/user/john→ Profile screen with userId='john'
myapp://settings/account→ Settings with section='account'
type ProductScreenProps = NativeStackScreenProps<
RootStackParamList,
'Product'
>;
function ProductScreen({ route, navigation }: ProductScreenProps) {
const { id } = route.params;
useEffect(() => {
// Fetch product with id
fetchProduct(id);
}, [id]);
return (
<View>
<Text>Product {id}</Text>
</View>
);
}Deferred deep links handle cases where users click a link before installing your app. When they install and open the app for the first time, it should navigate to the intended screen. Redirectly makes this seamless.
import { NavigationContainer } from '@react-navigation/native';
import { useEffect } from 'react';
import Redirectly from '@redirectly/react-native';
const linking = {
prefixes: ['myapp://', 'https://myapp.com'],
config: {
screens: {
Home: '',
Product: 'product/:id',
User: 'user/:userId',
},
},
// Custom async handler for deferred deep links
async getInitialURL() {
// 1. Check if app was opened from a deep link (cold start)
const url = await Linking.getInitialURL();
if (url != null) {
return url;
}
// 2. Check Redirectly for deferred deep link
try {
const redirectlyUrl = await Redirectly.getInitialURL();
return redirectlyUrl;
} catch (error) {
console.error('Error fetching from Redirectly:', error);
}
// 3. Default to home screen
return undefined;
},
};
function RootNavigator() {
useEffect(() => {
// Handle deep links when app is already running
const unsubscribe = Linking.addEventListener('url', ({ url }) => {
// Use React Navigation to navigate
const route = url.replace(/.*?:/\//, '');
// Navigation logic here
});
return unsubscribe.remove;
}, []);
return (
<NavigationContainer linking={linking}>
{/* Navigation stacks */}
</NavigationContainer>
);
}Benefit: Redirectly handles the deferred deep link attribution, so your app knows the original intent even if the user installed from an app store link.
Using TypeScript ensures type safety throughout your deep linking implementation. Here's a complete, production-ready example.
import { NavigationProp, useNavigation } from '@react-navigation/native';
import { NativeStackScreenProps } from '@react-navigation/native-stack';
// Type-safe parameter definitions
export type RootStackParamList = {
Home: undefined;
Product: { id: string; category?: string };
UserProfile: { userId: string; tab?: 'orders' | 'reviews' };
Search: { query: string };
NotFound: undefined;
};
// Screen-specific props type
export type ProductScreenProps = NativeStackScreenProps<
RootStackParamList,
'Product'
>;
// Complete linking configuration with full type safety
export const linking = {
prefixes: ['myapp://', 'https://myapp.com', 'https://www.myapp.com'],
config: {
screens: {
Home: {
path: '',
},
Product: {
path: 'product/:id',
parse: {
id: (id: string) => id,
category: (category?: string) => category,
},
},
UserProfile: {
path: 'user/:userId',
parse: {
userId: (userId: string) => userId,
tab: (tab?: string) => tab as 'orders' | 'reviews' | undefined,
},
},
Search: {
path: 'search/:query',
parse: {
query: (query: string) => decodeURIComponent(query),
},
},
NotFound: '*',
},
},
};
// Type-safe hook for navigation
export function useTypedNavigation() {
return useNavigation<NavigationProp<RootStackParamList>>();
}
// Example screen component
function ProductScreen({ route }: ProductScreenProps) {
const { id, category } = route.params;
return (
<View>
<Text>Product: {id}</Text>
{category && <Text>Category: {category}</Text>}
</View>
);
}
// Example programmatic navigation with type checking
function navigateToProduct(navigation: any, id: string, category?: string) {
navigation.navigate('Product', {
id,
category,
});
}# First, build and run your app in iOS simulator npx react-native run-ios # Then open a deep link in the simulator xcrun simctl openurl booted myapp://product/123 # Or with universal links xcrun simctl openurl booted https://myapp.com/product/123
Universal links require a valid apple-app-site-association file on your server. For testing:
# Check if your AASA file is properly configured # Open in Safari on iOS device: https://yourdomain.com/apple-app-site-association # Validate with our AASA validator # Visit: https://redirectly.app/aasa-validator
# Build and run app on Android emulator npx react-native run-android # Open a deep link using adb adb shell am start -W -a android.intent.action.VIEW -d myapp://product/123 # Or with universal links adb shell am start -W -a android.intent.action.VIEW -d https://myapp.com/product/123
App Links require a valid assetlinks.json file on your server.
# Validate your assetlinks.json with: # Visit: https://redirectly.app/assetlinks-validator # Check digital asset links on device adb shell am start -a android.intent.action.VIEW -d https://yourdomain.com/product/123
Deep linking with React Navigation v6 is a powerful way to improve user engagement and create seamless app experiences. By following this guide and testing thoroughly on both platforms, you'll have a robust deep linking implementation that handles both standard and deferred deep links.