Diagnosing React Navigation Deep Link Failures: Config Mismatches, Platform Setup, and Ordered Checks
Learn how to pinpoint why deep links open the wrong screen or fail entirely, with ordered checks, config validation, and platform‑specific fixes.
06 Jul 2026, 18:11 UTC

Recognizable Condition
Tapping a deep link either opens the app on the initial route, or the OS shows no app chooser at all. In both cases the root cause is almost always a mismatch between the incoming URL and the linking.config.screens path mapping.
Cause Table
| Symptom | Likely Cause |
|---|---|
| URL opens browser instead of app | Intent filter (Android) or Associated Domains (iOS) not registered |
| App opens but lands on wrong screen | screens path pattern doesn’t match URL segments |
| Works on Android, fails on iOS | Universal Links require a valid apple-app-site-association served over HTTPS |
Params are undefined | Path pattern missing :param placeholders |
Ordered Checks
1. Verify OS Delivery
Before touching JavaScript, confirm the platform actually hands the link to your app.
- Android (device/emulator, USB debugging enabled):
Expected: the app launches andadb shell am start -W -a android.intent.action.VIEW -d "https://example.com/product/42" com.myappActivityManagerreportsStatus=ok. - iOS (simulator, Xcode CLI tools installed):
Expected: the app becomes foreground and the delegate receives the URL.xcrun simctl openurl booted "https://example.com/product/42"
Risk: Running these commands on a release build without proper intent‑filter / associated‑domain setup will still open the app but fall back to the initial route, masking the real issue.
2. Confirm prefixes Exact Match
The prefixes array in the linking config is matched literally, including trailing slash and www vs apex domain.
const linking = {
prefixes: ["https://example.com", "myapp://"],
config: {
screens: {
Product: "product/:id",
Profile: "user/:username",
NotFound: "*",
},
},
};
If you test https://www.example.com/product/42 but only listed https://example.com, the prefix check fails and the link is treated as a cold‑start URL with no routing.
3. Validate Mapping with getStateFromPath
Export the helper from @react-navigation/native and run it in a Node test or the Metro console:
import { getStateFromPath } from '@react-navigation/native';
const testUrl = 'https://example.com/product/42';
const state = getStateFromPath(testUrl, linking.config);
console.log(state);
// Expected:
// { routes: [{ name: 'Product', params: { id: '42' } }], index: 0 }
- Null result → path pattern mismatch; adjust
config.screensor add a wildcard*route. - State present but wrong route → check for duplicate patterns or missing
:paramplaceholders.
This step isolates the config bug without a device.
Fix Mapping
| Finding | Fix |
|---|---|
getStateFromPath returns null | Add/adjust config.screens paths; include a NotFound: "*" fallback. |
| State correct but navigation doesn’t occur | Ensure NavigationContainer receives the linking prop and no custom getStateFromPath override swallows the URL. |
| Cold‑start link lost | Handle getInitialURL (or Linking.getInitialURL()) before any splash‑screen navigation; await the linking state resolution. |
Escalation Criteria
- Links work in debug builds but fail in release → verify ProGuard / R8 rules keep the auto‑verify intent filter and that the release signing key matches the
assetlinks.jsonon the server. - Cold‑start links consistently drop → add logging around
getInitialURLand theNavigationContaineronReadycallback to confirm the linking state resolves before any manual navigation. - Universal Links still not verified on iOS → serve
apple-app-site-associationwithapplication/jsonover HTTPS, no redirects, and ensureassociatedDomainsentitlement includesapplinks:example.com.
Verification Steps
- Quick device test:
npx uri-scheme open "https://example.com/product/42" --android(or--ios) against a dev build. Observe which screen renders. - Unit test the config: Call
getStateFromPath(url, config)in a Jest test and assert route name and params. - Android domain verification:
adb shell pm get-app-links com.myappshould showverified: truefor your domain. - Cold vs warm start: Kill the app, tap the link (cold). Then background the app, tap the link (warm). Log the resolved navigation state in both cases.
Limitations & Practical Check
React Navigation 5/6/7 differ in default option names (e.g., getStateFromPath vs getPathFromState) and in how the linking prop merges with the container. Confirm the exact API against the installed version’s TypeScript definitions before copying snippets. Custom‑scheme links (myapp://) work without server‑hosted verification files but are unverified and can be hijacked; prefer HTTPS universal links/App Links for production.
Practical sanity check: After applying fixes, run the uri-scheme command for both cold and warm starts and verify the logged navigation state matches the expected route and params. If the state matches but the UI doesn’t update, inspect the navigation listener order and any conditional rendering that might swallow the navigation action.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.