{"slug":"mobile-deep-linking-app-links","title":"mobile-deep-linking-app-links","summary":"Deep linking patterns - Universal Links (iOS), App Links (Android), URI schemes, expo-linking API, React Navigation linking config, Expo Router automatic linking, AASA/assetlinks.json setup, deferred deep links, testing","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-09-29T15:28:13.47308Z","repo":{"url":"https://github.com/agents-inc/skills","stars":24,"forks":8,"license":"MIT","updatedAt":"2026-09-07T17:50:55Z"},"bodyHtml":"<hr>\n<h2>name: mobile-deep-linking-app-links\ndescription: Deep linking patterns - Universal Links (iOS), App Links (Android), URI schemes, expo-linking API, React Navigation linking config, Expo Router automatic linking, AASA/assetlinks.json setup, deferred deep links, testing</h2>\n<h1>Deep Linking &amp; App Links Patterns</h1>\n<blockquote>\n<p><strong>Quick Guide:</strong> Universal Links (iOS) and App Links (Android) are the gold standard -- they use HTTPS URLs that open your app directly or fall back to the website. Custom URI schemes (<code>myapp://</code>) are simpler but less reliable (no fallback, can be hijacked). Use <code>expo-linking</code> for URL handling (<code>useURL</code>, <code>createURL</code>, <code>parse</code>). Expo Router handles deep linking automatically. React Navigation requires a <code>linking</code> config. Always test on real devices -- simulators miss edge cases.</p>\n</blockquote>\n<hr>\n<p>&lt;critical_requirements&gt;</p>\n<h2>CRITICAL: Before Using This Skill</h2>\n<blockquote>\n<p><strong>All code must follow project conventions in CLAUDE.md</strong> (kebab-case, named exports, import ordering, <code>import type</code>, named constants)</p>\n</blockquote>\n<p><strong>(You MUST use Universal Links (iOS) and App Links (Android) for production apps -- custom URI schemes have no fallback and can be hijacked by other apps)</strong></p>\n<p><strong>(You MUST host AASA and assetlinks.json over HTTPS at <code>/.well-known/</code> -- Apple and Google will reject HTTP or incorrectly hosted files)</strong></p>\n<p><strong>(You MUST handle all three app states: cold start (app not running), background (app suspended), and foreground (app active) -- missing any state causes dropped links)</strong></p>\n<p><strong>(You MUST test deep links on real devices -- simulators and emulators do not fully replicate OS-level link handling behavior)</strong></p>\n<p><strong>(You MUST never pass sensitive data (tokens, passwords) in deep link URLs -- URLs are logged, cached, and visible in browser history)</strong></p>\n<p>&lt;/critical_requirements&gt;</p>\n<hr>\n<p><strong>Auto-detection:</strong> deep link, deep linking, universal link, app link, URI scheme, custom scheme, expo-linking, Linking.useURL, Linking.createURL, Linking.parse, Linking.openURL, Linking.getInitialURL, linking config, apple-app-site-association, AASA, assetlinks.json, intentFilters, associatedDomains, deferred deep link, App Clip, Instant App, getInitialURL, addEventListener url</p>\n<p><strong>When to use:</strong></p>\n<ul>\n<li>Setting up Universal Links (iOS) or App Links (Android) for HTTPS-based deep linking</li>\n<li>Configuring custom URI schemes for development or simple deep linking</li>\n<li>Handling incoming URLs across cold start, background, and foreground app states</li>\n<li>Configuring React Navigation linking config or using Expo Router automatic linking</li>\n<li>Hosting and validating AASA (iOS) or assetlinks.json (Android) verification files</li>\n<li>Implementing deferred deep links (link -&gt; store -&gt; install -&gt; content)</li>\n<li>Testing deep links with CLI tools (<code>adb</code>, <code>xcrun simctl</code>, <code>uri-scheme</code>)</li>\n</ul>\n<p><strong>Key patterns covered:</strong></p>\n<ul>\n<li>Universal Links (iOS) and App Links (Android) end-to-end setup</li>\n<li>Custom URI scheme configuration and handling</li>\n<li>expo-linking API: <code>useURL</code>, <code>createURL</code>, <code>parse</code>, <code>getInitialURL</code></li>\n<li>React Navigation <code>linking</code> config with path mapping, parameter parsing, nested navigators</li>\n<li>Expo Router automatic deep linking (zero-config)</li>\n<li>AASA and assetlinks.json file format, hosting, and validation</li>\n<li>Handling incoming links in all app states</li>\n<li>Deferred deep linking concepts and implementation approaches</li>\n<li>Testing deep links with platform CLI tools</li>\n</ul>\n<p><strong>When NOT to use:</strong></p>\n<ul>\n<li>Web-only routing without a native mobile app</li>\n<li>Push notification routing (handle in your notification skill, not deep linking)</li>\n<li>App-to-app communication via intents/activities (use your native modules skill)</li>\n</ul>\n<p><strong>Detailed Resources:</strong></p>\n<ul>\n<li><a href=\"examples/core.md\">examples/core.md</a> - URI schemes, expo-linking API, handling incoming URLs, React Navigation linking config, Expo Router</li>\n<li><a href=\"examples/verification-files.md\">examples/verification-files.md</a> - AASA file (iOS), assetlinks.json (Android), hosting requirements, validation</li>\n<li><a href=\"examples/testing.md\">examples/testing.md</a> - Testing with adb, xcrun simctl, uri-scheme, debugging tips</li>\n<li><a href=\"reference.md\">reference.md</a> - API quick reference, linking config shape, testing commands</li>\n</ul>\n<hr>\n\n<hr>\n\n<hr>\n<p>&lt;decision_framework&gt;</p>\n<h2>Decision Framework</h2>\n<h3>Which Link Type to Use</h3>\n<pre><code>Is the app already installed on the target device?\n+-- Unknown/Maybe -&gt; Use Universal Links / App Links (HTTPS)\n|   +-- Needs fallback to website? -&gt; YES, this is why HTTPS links are preferred\n|   +-- Needs app store redirect? -&gt; Implement deferred deep linking\n+-- YES (guaranteed, e.g. internal tool) -&gt; Custom URI scheme is acceptable\n+-- NO (acquisition funnel) -&gt; Deferred deep link via attribution service\n\nDo you need the OS to open your app without a disambiguation dialog?\n+-- YES -&gt; Universal Links (iOS) / App Links (Android) with verified domains\n+-- NO  -&gt; Custom URI scheme (shows \"Open with...\" on some devices)\n</code></pre>\n<h3>Navigation Integration</h3>\n<pre><code>Which router are you using?\n+-- Expo Router -&gt; Automatic. No configuration needed. File paths = deep links.\n+-- React Navigation (static API) -&gt; Add `linking` property per screen definition\n+-- React Navigation (dynamic API) -&gt; Pass `linking` prop to NavigationContainer\n+-- Custom navigation -&gt; Use expo-linking useURL hook + manual navigation logic\n</code></pre>\n<h3>Link Type Comparison</h3>\n<table>\n<thead>\n<tr>\n<th>Feature</th>\n<th>Custom URI Scheme</th>\n<th>Universal Links (iOS)</th>\n<th>App Links (Android)</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Format</td>\n<td><code>myapp://path</code></td>\n<td><code>https://domain/path</code></td>\n<td><code>https://domain/path</code></td>\n</tr>\n<tr>\n<td>Fallback</td>\n<td>None (fails silently)</td>\n<td>Opens website</td>\n<td>Opens website</td>\n</tr>\n<tr>\n<td>Verification</td>\n<td>None</td>\n<td>AASA file on server</td>\n<td>assetlinks.json on server</td>\n</tr>\n<tr>\n<td>Hijack risk</td>\n<td>Any app can register</td>\n<td>OS-verified, secure</td>\n<td>OS-verified, secure</td>\n</tr>\n<tr>\n<td>Setup complexity</td>\n<td>Low</td>\n<td>Medium</td>\n<td>Medium</td>\n</tr>\n<tr>\n<td>Works without install</td>\n<td>No</td>\n<td>Yes (opens website)</td>\n<td>Yes (opens website)</td>\n</tr>\n<tr>\n<td>Disambiguation dialog</td>\n<td>Sometimes</td>\n<td>Never (verified)</td>\n<td>Never (verified)</td>\n</tr>\n</tbody>\n</table>\n<p>&lt;/decision_framework&gt;</p>\n<hr>\n<p>&lt;red_flags&gt;</p>\n<h2>RED FLAGS</h2>\n<p><strong>High Priority Issues:</strong></p>\n<ul>\n<li>Using custom URI schemes in production without Universal Links / App Links -- no fallback when app is not installed, links fail silently</li>\n<li>Missing <code>autoVerify: true</code> on Android intent filters -- without it, App Links behave as regular deep links (disambiguation dialog shown)</li>\n<li>Hosting AASA or assetlinks.json over HTTP instead of HTTPS -- Apple and Google reject non-HTTPS verification files</li>\n<li>Not handling cold start URLs -- <code>useURL</code> handles this, but manual implementations that only use <code>addEventListener</code> will miss the launch URL</li>\n<li>Passing sensitive data (auth tokens, passwords, PII) in deep link URLs -- URLs are logged in analytics, cached by CDNs, visible in browser history</li>\n</ul>\n<p><strong>Medium Priority Issues:</strong></p>\n<ul>\n<li>Not including <code>https://</code> prefix in React Navigation linking <code>prefixes</code> array -- Universal Links/App Links will not be matched</li>\n<li>Forgetting to rebuild after changing URI scheme or associated domains -- these are native-level changes that require a new build</li>\n<li>Not setting <code>initialRouteName</code> in nested navigator linking config -- back navigation will not work correctly from deep-linked screens</li>\n<li>Hardcoding development tunnel URLs in production builds -- use environment-specific prefix arrays</li>\n</ul>\n<p><strong>Gotchas &amp; Edge Cases:</strong></p>\n<ul>\n<li>iOS caches AASA files for up to 24 hours -- changes to the file will not take effect immediately on devices that have already fetched it</li>\n<li>Universal Links do not work when typed directly into Safari's address bar -- they must be tapped from another app, Messages, Mail, or a webpage on a different domain</li>\n<li>Universal Links do not work when opened from the same domain -- a link on <code>example.com</code> pointing to <code>example.com/product/123</code> will NOT open the app</li>\n<li>Android App Links verification happens at install time -- if your server is down during install, verification fails and the link opens in the browser</li>\n<li><code>Linking.parse()</code> handles non-standard URL formats (like Expo Go URLs with <code>--</code> separators) -- use it instead of <code>new URL()</code> for consistency</li>\n<li>Expo Go uses <code>exp://</code> scheme with a different URL format (<code>exp://127.0.0.1:8081/--/path</code>) -- test with development builds for production-accurate behavior</li>\n<li>Wildcard paths in AASA (<code>*</code>) do not match <code>/</code> or <code>.</code> characters -- use multiple path entries if needed</li>\n<li>Deep links received while the app is in the background may arrive with a delay on Android due to Doze mode and battery optimization</li>\n</ul>\n<p>&lt;/red_flags&gt;</p>\n<hr>\n<p>&lt;critical_reminders&gt;</p>\n<h2>CRITICAL REMINDERS</h2>\n<blockquote>\n<p><strong>All code must follow project conventions in CLAUDE.md</strong></p>\n</blockquote>\n<p><strong>(You MUST use Universal Links (iOS) and App Links (Android) for production apps -- custom URI schemes have no fallback and can be hijacked by other apps)</strong></p>\n<p><strong>(You MUST host AASA and assetlinks.json over HTTPS at <code>/.well-known/</code> -- Apple and Google will reject HTTP or incorrectly hosted files)</strong></p>\n<p><strong>(You MUST handle all three app states: cold start (app not running), background (app suspended), and foreground (app active) -- missing any state causes dropped links)</strong></p>\n<p><strong>(You MUST test deep links on real devices -- simulators and emulators do not fully replicate OS-level link handling behavior)</strong></p>\n<p><strong>(You MUST never pass sensitive data (tokens, passwords) in deep link URLs -- URLs are logged, cached, and visible in browser history)</strong></p>\n<p><strong>Failure to follow these rules will result in broken deep links, security vulnerabilities, and poor user experience when links fail silently.</strong></p>\n<p>&lt;/critical_reminders&gt;</p>\n","files":[{"path":"examples/core.md","sizeBytes":12737,"isText":true},{"path":"examples/testing.md","sizeBytes":7329,"isText":true},{"path":"examples/verification-files.md","sizeBytes":7657,"isText":true},{"path":"reference.md","sizeBytes":3083,"isText":true},{"path":"SKILL.md","sizeBytes":16350,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-09-29T15:30:59.276708Z","sha256":"B2CE5CBCCFB81DC3225F3A791DCB8DE346A73573136B50AEB6728D0F8EFD3621","sizeBytes":17153},"review":null,"source":{"repositoryUrl":"https://github.com/agents-inc/skills","path":"dist/plugins/mobile-deep-linking-app-links/skills/mobile-deep-linking-app-links","license":"MIT","commit":"3a51ef571e996b18294bf776d53dbdad26de0617","subtreeSha":"F30AA5974C701076D63B9271F67C052F6450A97222D17D552CF6BD9E2819C5C2","lastSyncedAt":"2026-09-29T15:27:48.914434Z"},"reviewedAt":"2026-09-29T15:37:06.564492Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/agents-inc/skills/tree/main/dist/plugins/mobile-deep-linking-app-links/skills/mobile-deep-linking-app-links"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install agents-inc-skills@llmmart"},{"target":"git","command":"git clone https://github.com/agents-inc/skills.git"}]}