Back to Blog
React Native
12 min read
March 1, 2024

React Native Deep Linking with React Navigation v6: Complete Tutorial

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 (
    <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.

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 (
    <View>
      <Text>Product {id}</Text>
    </View>
  );
}

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<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,
  });
}

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

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 Articles