# React Native Deep Linking with React Navigation v6: Complete Tutorial
> Master deep linking in React Native with React Navigation v6. Learn linking config, screen mapping, TypeScript examples, and testing strategies.
- Canonical: https://redirectly.app/blog/react-native-deep-linking-react-navigation
- Site: [Redirectly](https://redirectly.app) — deferred deep linking for Flutter & React Native
- Published: 2024-03-01
- Author: Redirectly
- Category: React Native · 8 min read
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.
## Understanding Deep Linking in React Native
**What is Deep Linking in React Native?**
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.
### URL Schemes (Custom Protocol)
Custom URL schemes like 'myapp://' let you handle app-specific links.
`myapp://product/123` — Works when app is installed
### Universal Links & App Links
Standard HTTPS URLs with fallback to web when app isn't installed.
`https://myapp.com/product/123` — Seamless 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 Linking Configuration
React Navigation v6 uses a 'linking' prop on the NavigationContainer to define how URLs map to navigation state.
### Basic Linking Configuration
```typescript
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 (
);
}
```
**Prefixes:** Define the URL schemes and domains your app responds to. Use wildcard subdomains for branch links or deep link services.
## Screen Mapping from URL Paths
React Navigation maps URL patterns to screens and extracts parameters. The 'config.screens' object defines these mappings using route patterns.
### Advanced Configuration Example
```typescript
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: '*',
},
},
};
```
### URL Examples
- `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'
### Accessing Parameters
```typescript
type ProductScreenProps = NativeStackScreenProps<
RootStackParamList,
'Product'
>;
function ProductScreen({ route, navigation }: ProductScreenProps) {
const { id } = route.params;
useEffect(() => {
// Fetch product with id
fetchProduct(id);
}, [id]);
return (
Product {id}
);
}
```
## Handling Deferred Deep Links with Redirectly
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.
### Integrating Redirectly for Deferred Deep Links
```typescript
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 (
{/* Navigation stacks */}
);
}
```
**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.
### Learn More About Deferred Deep Linking
[Read the Complete Deferred Deep Linking Guide](https://redirectly.app/react-native-deferred-deep-linking.md)
## TypeScript Implementation
Using TypeScript ensures type safety throughout your deep linking implementation. Here's a complete, production-ready example.
```typescript
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>();
}
// Example screen component
function ProductScreen({ route }: ProductScreenProps) {
const { id, category } = route.params;
return (
Product: {id}
{category && Category: {category}}
);
}
// Example programmatic navigation with type checking
function navigateToProduct(navigation: any, id: string, category?: string) {
navigation.navigate('Product', {
id,
category,
});
}
```
## Testing Deep Links on iOS and Android
### iOS Testing
**Method 1: Using Simulator**
```bash
# 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
```
**Method 2: Testing Universal Links**
Universal links require a valid apple-app-site-association file on your server. For testing:
```bash
# 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
```
### Android Testing
**Method 1: Using Android Emulator**
```bash
# 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
```
**Method 2: Testing App Links**
App Links require a valid assetlinks.json file on your server.
```bash
# 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
```
### Testing Checklist
- Test cold start (app not running)
- Test warm start (app already running)
- Test with invalid/non-existent IDs
- Test with special characters in parameters
- Test both URL schemes and universal/app links
- Test on real devices and emulators
- Verify AASA/assetlinks files are accessible
- Test deferred deep links with fresh install
## Related Resources & Tools
- [AASA Validator](https://redirectly.app/aasa-validator.md) — Validate your apple-app-site-association configuration
- [Assetlinks Validator](https://redirectly.app/assetlinks-validator.md) — Validate your assetlinks.json for Android App Links
- [Deferred Deep Links](https://redirectly.app/react-native-deferred-deep-linking.md) — Complete guide to handling deferred deep links
## Master Deep Linking Today!
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.
## Related
- [Universal Links in React Native: Complete iOS Setup](https://redirectly.app/blog/react-native-universal-links-ios.md) — Deep dive into setting up Universal Links on iOS with AASA configuration and best practices.
- [What is Deferred Deep Linking?](https://redirectly.app/blog/what-is-deferred-deep-linking.md) — Learn how deferred deep linking works and why it's essential for post-install user experiences.
---
Full site index for AI agents: https://redirectly.app/llms.txt