# Universal Links in React Native: Complete iOS Setup Guide > Master Universal Links in React Native with Xcode configuration, AASA file setup, entitlements, AppDelegate code, and real device testing on iOS. - Canonical: https://redirectly.app/blog/react-native-universal-links-ios - Site: [Redirectly](https://redirectly.app) — deferred deep linking for Flutter & React Native - Published: 2024-03-05 - Author: Redirectly - Category: React Native · 8 min read Learn how to implement Universal Links in React Native with comprehensive Xcode configuration, AASA file setup, and best practices for iOS deep linking. Master associated domains, entitlements, and real device testing. ## What Are Universal Links? **Definition** Universal Links are HTTP or HTTPS URLs that seamlessly open your iOS app if it's installed, or open the website if it's not. They're Apple's standard way of handling deep links on iOS, providing a smooth user experience without the app chooser dialog. ### Universal Links Benefits - No app chooser dialog - Works with HTTPS URLs - Fallback to website - Seamless user experience - Shareable standard URLs - Great for marketing ### URL Schemes Comparison - **Custom Schemes** — `myapp://product/123` — Only if app installed - **Universal Links** — `https://myapp.com/product/123` — App or website ## How Universal Links Work in React Native ### The Universal Link Flow 1. **User Clicks Link** — User taps an HTTPS link from Safari, email, or another app 2. **iOS Checks AASA File** — iOS fetches apple-app-site-association from your domain 3. **Domain Verification** — iOS verifies domain is associated with your app via team ID and bundle ID 4. **App Opens Directly** — If verified, app opens and receives the URL. Otherwise, Safari opens the URL **Key Point:** iOS caches the AASA file, so changes may take time to propagate on devices. For new domains, it can take up to 2-3 hours to fully cache. ## Xcode Configuration & Entitlements ### Step 1: Add Associated Domains Entitlement In Xcode: 1. Open your project in Xcode 2. Select your app target 3. Go to "Signing & Capabilities" tab 4. Click "+ Capability" 5. Search for and add "Associated Domains" 6. Under "Domains", add your domain with "applinks:" prefix ```text applinks:myapp.com applinks:www.myapp.com applinks:api.myapp.com # For subdomains applinks:*.myapp.com ``` **Note:** Include all domain variants you might use. iOS is strict about domain matching. ### Step 2: Verify Entitlements.plist Xcode automatically generates an Entitlements.plist file. Verify it contains: ```xml com.apple.developer.associated-domains applinks:myapp.com applinks:www.myapp.com ``` ### Step 3: Update Build Settings Required Settings: - **Code Signing Identity:** Apple Development certificate - **Team ID:** Must match your Apple Developer account - **Bundle Identifier:** Must be registered in Apple Developer - **Provisioning Profile:** Includes "Associated Domains" capability ## Creating the AASA File The apple-app-site-association (AASA) file is a JSON file hosted on your domain that tells iOS which paths your app should handle. This is the critical piece that enables Universal Links. ### AASA File Structure ```json { "applinks": { "apps": [], "details": [ { "appID": "ABC123DEF456.com.myapp.ios", "paths": [ "/product/*", "/user/:userId", "/search/*", "NOT /admin/*" ] }, { "appID": "ABC123DEF456.com.myapp.beta", "paths": [ "/beta/*" ] } ] } } ``` **Find Your App ID:** Your appID is Team ID (from Apple Developer) + Bundle Identifier Example: ABC123DEF456.com.myapp.ios ### File Hosting Requirements **Location: /.well-known/** ```text https://myapp.com/.well-known/apple-app-site-association https://www.myapp.com/.well-known/apple-app-site-association ``` The file must be served from all domain variants you add to Associated Domains. **HTTP Headers Required:** ```bash # Nginx example location /.well-known/apple-app-site-association { add_header Content-Type application/json; add_header Access-Control-Allow-Origin *; } # Apache example Header set Content-Type "application/json" Header set Access-Control-Allow-Origin "*" ``` ### Validate Your AASA File Use Our AASA Validator Tool — [Check your AASA configuration](https://redirectly.app/aasa-validator.md) ## React Native Code Implementation Set up React Navigation to handle incoming universal links from iOS. ```typescript import { NavigationContainer, useNavigation } from '@react-navigation/native'; import { Linking } from 'react-native'; import { useEffect } from 'react'; const linking = { prefixes: ['https://myapp.com', 'https://www.myapp.com'], config: { screens: { Home: '', Product: 'product/:id', UserProfile: 'user/:userId', NotFound: '*', }, }, async getInitialURL() { // Handle notification-opened while app was killed const url = await Linking.getInitialURL(); if (url != null) { return url; } }, }; export function RootNavigator() { const navigationRef = useRef(null); useEffect(() => { const listening = Linking.addEventListener('url', ({ url }) => { navigationRef.current?.resetRoot({ index: 0, routes: [ { name: 'Home', state: { routes: [ { name: 'Product', params: { id: extractProductId(url) }, }, ], }, }, ], }); }); return () => listening.remove(); }, []); return ( {/* Your screens */} ); } function extractProductId(url: string): string { return url.split('/product/')[1]; } ``` ## AppDelegate Configuration In some cases, you may need to manually handle deep links in AppDelegate, especially if using custom RCTLinkingManager handling. ```swift import UIKit import React @main class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { let rootViewController = RCTRootViewController() rootViewController.bundle = Bundle(url: jsCodeLocation) self.window = UIWindow(frame: UIScreen.main.bounds) self.window?.rootViewController = rootViewController self.window?.makeKeyAndVisible() return true } // Handle universal links func application( _ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void ) -> Bool { if userActivity.activityType == NSUserActivityTypeBrowsingWeb, let url = userActivity.webpageURL { // React Navigation will handle the routing RCTLinkingManager.application(application, open: url, sourceApplication: nil, annotation: [:]) return true } return false } } ``` ## Testing on Real Devices ### Method 1: Testing via Notes App 1. Build and deploy your app to a real device 2. Open Notes app on the device 3. Create a new note and paste your universal link 4. Long-press the link (don't tap yet) 5. Select "Open" or tap the link 6. App should open directly to the intended screen `https://myapp.com/product/123` ### Method 2: Testing via Safari 1. Open Safari on your test device 2. Navigate to your universal link URL 3. iOS should intercept and open your app 4. If it opens the website instead, check AASA file 5. Try force-closing the app and retesting **Wait 2-3 hours:** iOS caches AASA files. New domains may not work immediately. ### Method 3: Using Apple's Diagnostic Tool ```bash # On your Mac, check the device logs log stream --predicate 'eventMessage contains[c] "universal link" or eventMessage contains[c] "applinks"' --level debug # Or check via Xcode Console while running # Build & Run your app, then view Console output for linking events ``` ## Common Issues & Fixes ### Issue: Link Opens in Safari Instead of App **Possible Causes:** - AASA file not properly hosted - Domain not in Associated Domains list - Incorrect App ID in AASA file - AASA file not cached yet **Solutions:** - Verify AASA file with our validator tool - Check Content-Type header is application/json - Force remove app from device and reinstall - Wait 2-3 hours for iOS cache to clear - Add domain to Settings → Developer on device ### Issue: App Crashes When Opening Deep Link **Possible Causes:** - URL parameters not properly extracted - Navigation route doesn't exist - Missing error handling in navigation **Solutions:** - Add error boundaries in navigation - Log incoming URLs: console.log(url) - Validate parameters before navigation - Use try-catch in getInitialURL handler ### Issue: Associated Domains Capability Missing **Solutions:** - In Xcode, go to Signing & Capabilities - Click + Capability and search "Associated" - Add your domain with applinks: prefix - Ensure provisioning profile includes capability - Clean build folder and rebuild ### Need Help? Use our AASA Validator to check your configuration: [Validate Your AASA File](https://redirectly.app/aasa-validator.md) ## Deferred Deep Links Integration **Combine with Deferred Deep Links** Universal Links are great for installed apps, but for users who don't have your app yet, deferred deep links ensure they get the intended experience after installation. [Learn About Deferred Deep Links](https://redirectly.app/react-native-deferred-deep-linking.md) ## Universal Links are Worth It! Setting up Universal Links takes effort, but it significantly improves the user experience by providing seamless navigation from web to app. Combined with proper deferred deep linking, you have a complete solution for all user scenarios. ## Related - [React Native Deep Linking with React Navigation v6](https://redirectly.app/blog/react-native-deep-linking-react-navigation.md) — Master deep linking in React Native with linking configuration, TypeScript examples, and testing strategies. - [What is Deferred Deep Linking?](https://redirectly.app/blog/what-is-deferred-deep-linking.md) — Learn how deferred deep linking works and how to implement it in your React Native app. --- Full site index for AI agents: https://redirectly.app/llms.txt