Key takeaways
- React Native deep linking has three layers: iOS Universal Links, Android App Links, and a JavaScript routing layer on top, which is what makes it more involved than native setup.
- A deep linking SDK handles the parts React Native's built-in
LinkingAPI does not: hosting the AASA andassetlinks.jsonfiles, deferred deep linking across the install gap, and attribution. - Deferred deep linking is the hard part. React Native's
Linkingcan route a link when the app is installed, but it cannot carry context through a fresh install. That requires an SDK. - This guide covers a working setup: native configuration for both platforms, SDK initialization, link handling, and a deferred-linking flow.
- Start free for link creation on the free tier; deferred deep linking and install attribution are on paid plans from $9/month.
Short answer: To implement deep linking in React Native, configure iOS Universal Links and Android App Links natively, then use a deep linking SDK to host the verification files, handle deferred deep linking through fresh installs, and pass link parameters into your React Navigation routing. React Native's built-in
LinkingAPI alone cannot do deferred linking.
Why React Native deep linking needs more than Linking
React Native ships a Linking API that can open the app from a URL and read the initial URL. That covers the simplest case: the app is already installed and you just need to route. It does not cover the cases that actually drive growth:
- Deferred deep linking. A new user taps a link, does not have the app, installs it, and should land on the linked content with referral context intact.
Linkinghas no memory across the install. - Verification file hosting. iOS needs a correctly served
apple-app-site-association(AASA) file; Android needsassetlinks.json. Serving and maintaining these is error-prone by hand. - Attribution. Tying a click to an install to an in-app event needs infrastructure beyond the OS APIs.
A deep linking SDK fills these gaps.
Step 1: Configure iOS Universal Links
In Xcode, enable the Associated Domains capability and add your branded domain:
applinks:go.yourbrand.com
Your platform hosts the AASA file at https://go.yourbrand.com/.well-known/apple-app-site-association. With Ulinkly, this is generated and hosted automatically once you enter your Bundle ID and Apple Team ID in the project configuration.
Step 2: Configure Android App Links
In AndroidManifest.xml, add an intent filter with android:autoVerify="true":
<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="go.yourbrand.com" />
</intent-filter>
The matching assetlinks.json is hosted at https://go.yourbrand.com/.well-known/assetlinks.json, again generated for you after you provide the Package Name and SHA-256 fingerprint.
Step 3: Install and initialize the SDK
npx expo install @ulinkly/react-native
@ulinkly/react-native is an Expo native module that bridges the native iOS and Android Ulinkly SDKs, so it works in both Expo and bare React Native projects.
import ULink from '@ulinkly/react-native';
// Always await initialize() before calling any other method.
await ULink.initialize({ apiKey: 'ulk_your_api_key_here', debug: __DEV__ });
Step 4: Handle resolved links
import { useEffect } from 'react';
import ULink from '@ulinkly/react-native';
import { useNavigation } from '@react-navigation/native';
function useDeepLinks() {
const navigation = useNavigation();
useEffect(() => {
// onDynamicLink fires for cold-start, foreground, and deferred-install links.
const sub = ULink.onDynamicLink((data) => {
// data.parameters carries your routing payload; data.isDeferred flags a deferred match.
const screen = data.parameters?.screen;
if (screen) {
navigation.navigate(routeFor(screen), data.parameters);
}
});
return () => sub.remove();
}, [navigation]);
}
Step 5: Handle the deferred case on first launch
On a fresh install, trigger a deferred-link check once after initialization. checkDeferredLink() returns Promise<void> — it does not return the link directly. Instead, the matched click is delivered through the same onDynamicLink listener from Step 4 (with data.isDeferred === true), so your existing navigation logic handles it:
// After initialize(); skip this if you set autoCheckDeferredLink: true in the config.
await ULink.checkDeferredLink();
// The matched deferred link arrives via the onDynamicLink listener registered above.
Testing the four scenarios that break in production
- App installed, link tapped from native browser.
- App not installed (deferred), then installed and opened.
- Link opened inside a social in-app browser (Instagram, TikTok).
- Desktop fallback.
Test all four on real devices. The simulator does not reproduce install-gap behavior reliably.
FAQ
Can React Native do deep linking without an SDK?
Partially. The built-in Linking API handles routing when the app is already installed, but it cannot do deferred deep linking through a fresh install or host the required verification files. Those need an SDK.
What is the difference between a deep link and a deferred deep link in React Native?
A deep link routes an existing user to in-app content. A deferred deep link preserves that destination and its parameters through an install, so a brand-new user lands on the right screen the first time they open the app.
Does this work with Expo?
Yes. @ulinkly/react-native is built with the Expo Modules API, so it works in both Expo (with a development build / config plugin) and bare React Native projects. It does not run in Expo Go, because it bridges native iOS and Android code.
How do I handle Universal Links inside Instagram or TikTok?
Those apps use in-app browsers that often block Universal Links. A deep linking platform detects these environments and routes around them rather than failing silently.
Do I need separate setup for iOS and Android?
Yes for the native verification (Associated Domains on iOS, App Links intent filter on Android), but the SDK and your JavaScript link-handling code are shared across both.
Ship it
Create a free Ulinkly account, add your iOS and Android apps, and wire up the listener above. Deferred deep linking and install attribution are available on paid plans from $9/month.
