React Native

React Native Deep Linking Not Opening App on Cold Start

Deep links that work correctly while the app is already open, but fail to properly launch or route on a true cold start (app not running at all), almost always come down to missing initial-URL handling — the standard "listen for link events" pattern only captures links received while the app is already alive, and cold starts need a separate, explicit check.

The Problem

Tapping a deep link while the app is already running in the background correctly opens and navigates to the right screen. But tapping the exact same link when the app isn't running at all either does nothing, opens to the default home screen instead of the linked content, or crashes on launch.

Why It Happens

React Native's Linking API (and React Navigation's deep linking integration built on top of it) distinguishes between two genuinely different scenarios that require separate handling:

  • The app is already running, and a new link event arrives — handled by an event listener (Linking.addEventListener('url', ...)) that fires for links received while already alive
  • The app was launched fresh, specifically because of the link (a true cold start) — this requires explicitly checking Linking.getInitialURL(), since the event listener above only fires for links that arrive after the app has already started, not the one that caused the launch in the first place

Code that only implements the event listener, without also checking the initial URL on startup, works perfectly for the "app already running" case while silently failing for cold starts — which is exactly the split behavior described in the problem.

The Fix

If you're using React Navigation's built-in linking configuration, it handles both cases automatically as long as it's configured correctly — confirm your linking config is actually passed to the navigation container, and that getStateFromPath or your route mapping correctly covers the specific URL pattern being tested:

const linking = {
  prefixes: ['myapp://', 'https://myapp.com'],
  config: {
    screens: {
      Profile: 'user/:id',
    },
  },
};

<NavigationContainer linking={linking}>
  {/* navigators */}
</NavigationContainer>

If you're handling deep linking manually rather than through React Navigation's built-in support, explicitly check for an initial URL when the app first mounts, in addition to listening for subsequent link events:

useEffect(() => {
  // Handles links received while the app is already running
  const subscription = Linking.addEventListener('url', ({ url }) => {
    handleDeepLink(url);
  });

  // Handles the case where the app was launched BY this link (cold start)
  Linking.getInitialURL().then((url) => {
    if (url) {
      handleDeepLink(url);
    }
  });

  return () => subscription.remove();
}, []);

The getInitialURL() check is the piece that's commonly missing — without it, a cold-start link is effectively ignored, since there's no running event listener yet at the moment the app actually launches from that link.

If the deep link relies on the app's initial render already having happened before navigation can occur (for example, needing the navigation container to be mounted first), make sure your initial-URL handling waits for that readiness rather than firing immediately during a render where the navigator isn't ready to accept a navigation action yet:

const navigationRef = useNavigationContainerRef();

useEffect(() => {
  Linking.getInitialURL().then((url) => {
    if (url && navigationRef.isReady()) {
      // safe to navigate
    }
  });
}, []);

Confirm your app's native configuration (the intent filter on Android, the URL scheme association on iOS) is correctly set up to actually route the link to your app at the OS level in the first place — a cold-start-specific failure can sometimes actually be a native configuration issue rather than a JavaScript-side handling gap, if the OS itself isn't even routing the link to your app consistently when it isn't already running.

Still Not Working?

If getInitialURL() is implemented correctly but still returns null on a genuine cold-start link tap, test using the platform's own command-line tools to trigger the deep link directly, bypassing any potential issue with how you're testing (like tapping a link from a notes app that might handle it differently than a genuine external app-to-app link):

# Android
adb shell am start -W -a android.intent.action.VIEW -d "myapp://user/123"

# iOS Simulator
xcrun simctl openurl booted "myapp://user/123"

Testing this way, with the app fully force-closed beforehand, gives a clean, repeatable cold-start test that isolates whether the issue is in your app's link handling logic or in how a specific external source is triggering the link.