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
Basic Deep Link
bash
/template implement-deep-linking user/:idE-commerce Product Links
bash
/template implement-deep-linking product/:id --domain myapp.com --analyticsMultiple Routes
bash
/template implement-deep-linking post/:id
/template implement-deep-linking profile/:username
/template implement-deep-linking invite/:codeWhat 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 componentLinking 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
Universal Links (iOS 9+)
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
App Links (Android 6.0+)
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
Dynamic Links
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=123Deferred Deep Links
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');
}
}Link Validation
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
Track Deep Link Performance
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'),
};
}Testing Deep Links
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
- Validate All Links: Never trust incoming URLs
- Handle Errors: Gracefully handle invalid links
- Test Thoroughly: Test on real devices
- Track Performance: Monitor deep link success rates
- 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);
}Related Commands
add-screen- Create screens to link tosetup-push-notifications- Deep links in notificationssetup-shared-backend- Server-side link generation
