Deep links that survive a cold start on cheap Android hardware
A field supervisor tapped a link in a WhatsApp message and got the app's home screen. The same link worked fine when the app was already open. We heard this three times in one week from different clients.
The problem is almost always the same: the deep link handling lives in a `useEffect` inside the root component, and it reads the initial URL on mount. When the app is already running, React Native fires the `Linking` event and the handler catches it. When the app is cold-started from a link, the native side opens the app and passes the URL — but your JavaScript hasn't loaded yet. On a mid-range phone with 2GB RAM, the JS bundle takes 800ms to a second and a half to parse. By the time your component mounts, the initial URL event is gone.
The fix is to grab the initial URL from the native side before React even mounts, and hand it off once the app is ready.
For React Native 0.71 and above with the New Architecture, `getInitialURL` returns a promise that resolves with the launch URL. The key is not to wait for it inside a component — call it early and stash the result:
import { Linking } from 'react-native';
import { setInitialDeepLink } from './navigation';
let initialUrl = null;
Linking.getInitialURL().then((url) => {
initialUrl = url;
if (navigationReadyRef.current && url) {
handleDeepLink(url);
}
});
Linking.addEventListener('change', ({ url }) => {
if (url !== initialUrl) {
handleDeepLink(url);
}
});`navigationReadyRef` is set when your navigation container finishes mounting. The idea is that `getInitialURL` may resolve before or after the navigation is ready — you need to handle both orderings. If the URL resolves first, you hold it. If the navigation mounts first, you resolve the pending URL then.
On Android, there is a second trap. The intent that launched the app carries the deep link, but if the user opens the app from the launcher icon afterward, Android may deliver the same intent again. We have seen this on Samsung devices specifically. The fix is to clear the intent data in `MainActivity` after reading it:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val intent = this.intent
val deepLink = intent?.data?.toString()
intent?.data = null
setIntent(intent)
}This prevents the stale link from firing on the next cold start from the launcher.
For apps using `react-navigation` v6+, the library handles initial URL resolution if you pass the `linking` config to your container. But we have seen cases on low-end devices where even this fires too late — the navigation mounts, the deep link handler runs, but the screen it should navigate to hasn't registered its routes yet. The pragmatic fix is to add a small delay or a flag that waits for the first render cycle to complete before dispatching the navigation action. Not elegant, but it stops users landing on the wrong screen.
We test cold-start deep links by killing the app process, sending the link via `adb`, and verifying on a physical device. Emulators sometimes resolve the initial URL too quickly to catch the race.
The field supervisor's WhatsApp link now opens the right inspection form even after a full device reboot.