Skip to content

implement-deep-linking

Set up deep links and universal links for your React Native app.

Overview

The implement-deep-linking command configures deep linking for your React Native application, enabling users to open specific screens via URLs, supporting both custom URL schemes and universal/app links.

Usage

bash
/template implement-deep-linking <route-pattern> [options]

Parameters

  • <route-pattern> - URL pattern to handle (e.g., user/:id, product/:slug)

Options

  • --scheme - Custom URL scheme (default: app name)
  • --domain - Domain for universal links
  • --fallback - Web fallback URL
  • --analytics - Track deep link usage

Examples

bash
/template implement-deep-linking user/:id
bash
/template implement-deep-linking product/:id --domain myapp.com --analytics

Multiple Routes

bash
/template implement-deep-linking post/:id
/template implement-deep-linking profile/:username
/template implement-deep-linking invite/:code

What It Creates

Deep Linking Structure

src/
├── navigation/
│   ├── linking/
│   │   ├── config.ts      # Linking configuration
│   │   ├── handlers.ts    # Route handlers
│   │   ├── parsers.ts     # URL parsing utilities
│   │   └── types.ts       # TypeScript types
│   └── DeepLinkHandler.tsx # Deep link component

Linking Configuration

typescript
// src/navigation/linking/config.ts
export const linking: LinkingOptions = {
  prefixes: [
    'myapp://',
    'https://myapp.com',
    'https://www.myapp.com',
  ],
  
  config: {
    screens: {
      Home: {
        screens: {
          Feed: 'feed',
          Profile: 'profile/:username',
          Post: 'post/:id',
        },
      },
      Auth: {
        screens: {
          Login: 'login',
          Register: 'register',
          ResetPassword: 'reset-password/:token',
        },
      },
      NotFound: '*',
    },
  },
  
  async getInitialURL() {
    // Check if app was opened from a deep link
    const url = await Linking.getInitialURL();
    return url;
  },
  
  subscribe(listener) {
    // Listen to incoming links
    const subscription = Linking.addEventListener('url', ({ url }) => {
      listener(url);
    });
    
    return () => subscription.remove();
  },
};

Route Handlers

typescript
// src/navigation/linking/handlers.ts
export const deepLinkHandlers = {
  'product/:id': async ({ id }: { id: string }) => {
    // Pre-fetch product data
    await prefetchProduct(id);
    
    // Navigate to product screen
    navigation.navigate('Product', { id });
    
    // Track analytics
    analytics.track('deep_link_opened', {
      type: 'product',
      id,
    });
  },
  
  'invite/:code': async ({ code }: { code: string }) => {
    // Validate invite code
    const isValid = await validateInviteCode(code);
    
    if (isValid) {
      navigation.navigate('Register', { inviteCode: code });
    } else {
      showToast('Invalid invite code');
    }
  },
};

Platform Configuration

iOS Setup

json
// ios/MyApp/MyApp.entitlements
{
  "com.apple.developer.associated-domains": [
    "applinks:myapp.com",
    "applinks:www.myapp.com"
  ]
}

Info.plist Configuration

xml
<!-- ios/MyApp/Info.plist -->
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>myapp</string>
    </array>
  </dict>
</array>

Android Setup

xml
<!-- android/app/src/main/AndroidManifest.xml -->
<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.com" />
</intent-filter>

<!-- Custom scheme -->
<intent-filter>
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.DEFAULT" />
  <category android:name="android.intent.category.BROWSABLE" />
  
  <data android:scheme="myapp" />
</intent-filter>

Advanced Features

typescript
// Generate shareable links
export function createDynamicLink(screen: string, params: any) {
  const baseUrl = 'https://myapp.com';
  const queryString = new URLSearchParams(params).toString();
  
  return `${baseUrl}/${screen}?${queryString}`;
}

// Usage
const shareLink = createDynamicLink('product', { id: '123' });
// https://myapp.com/product?id=123
typescript
// Handle links for first-time users
export async function handleDeferredDeepLink() {
  const link = await AsyncStorage.getItem('deferred_deep_link');
  
  if (link && isUserOnboarded()) {
    // Process the link after onboarding
    await processDeepLink(link);
    await AsyncStorage.removeItem('deferred_deep_link');
  }
}
typescript
// Validate and sanitize incoming links
export function validateDeepLink(url: string): boolean {
  try {
    const parsed = new URL(url);
    
    // Check allowed domains
    const allowedHosts = ['myapp.com', 'www.myapp.com'];
    if (!allowedHosts.includes(parsed.hostname)) {
      return false;
    }
    
    // Validate parameters
    return validateRouteParams(parsed.pathname, parsed.searchParams);
  } catch {
    return false;
  }
}

Analytics Integration

typescript
// src/navigation/linking/analytics.ts
export function trackDeepLink(url: string, success: boolean) {
  const parsed = parseURL(url);
  
  analytics.track('deep_link_opened', {
    url,
    path: parsed.path,
    params: parsed.params,
    success,
    source: getSourceFromURL(url),
    timestamp: Date.now(),
  });
}

Attribution Tracking

typescript
// Track marketing campaign attribution
export function parseAttributionParams(url: string) {
  const params = new URLSearchParams(url.split('?')[1]);
  
  return {
    source: params.get('utm_source'),
    medium: params.get('utm_medium'),
    campaign: params.get('utm_campaign'),
    content: params.get('utm_content'),
  };
}

iOS Simulator

bash
# Open deep link in iOS simulator
xcrun simctl openurl booted "myapp://product/123"

# Universal link
xcrun simctl openurl booted "https://myapp.com/product/123"

Android Emulator

bash
# Open deep link in Android emulator
adb shell am start -W -a android.intent.action.VIEW -d "myapp://product/123"

# App link
adb shell am start -W -a android.intent.action.VIEW -d "https://myapp.com/product/123"

Testing Component

typescript
// __tests__/DeepLinking.test.tsx
describe('Deep Linking', () => {
  it('navigates to product screen', async () => {
    const { getByTestId } = render(<App />);
    
    await Linking.openURL('myapp://product/123');
    
    await waitFor(() => {
      expect(getByTestId('product-screen')).toBeTruthy();
      expect(getByTestId('product-id')).toHaveTextContent('123');
    });
  });
});

Best Practices

  1. Validate All Links: Never trust incoming URLs
  2. Handle Errors: Gracefully handle invalid links
  3. Test Thoroughly: Test on real devices
  4. Track Performance: Monitor deep link success rates
  5. Document Links: Maintain a registry of all deep links

Common Patterns

Authentication Required

typescript
// Redirect to login if needed
const requireAuth = (handler: Function) => {
  return async (params: any) => {
    if (!isAuthenticated()) {
      // Store intended destination
      await AsyncStorage.setItem('post_login_redirect', JSON.stringify({
        screen: 'Product',
        params,
      }));
      
      navigation.navigate('Login');
      return;
    }
    
    return handler(params);
  };
};

Onboarding Flow

typescript
// Handle links during onboarding
if (!isOnboarded()) {
  // Save link for later
  await AsyncStorage.setItem('pending_deep_link', url);
  navigation.navigate('Onboarding');
} else {
  processDeepLink(url);
}

Built with ❤️ for the AI Coding community, by Praney Behl