Skip to content
Development
Skill

/deep-linking

Deep links, universal links and app links in React Native. Use when links do not open the app, for apple-app-site-association or assetlinks.json, lost cold-start links, expo-router linking, custom URL schemes.

From plugin
claude-code-react-native
318 skills4 agents
Install
$ npx -y skills add AnilBurcu/claude-code-react-native --skill deep-linking --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/deep-linking

Context preview

The summary Claude sees to decide when to auto-load this skill.

Deep links, universal links and app links in React Native. Use when links do not open the app, for apple-app-site-association or assetlinks.json, lost cold-start links, expo-router linking, custom URL schemes.

SKILL.md

deep-linking.SKILL.md
name: deep-linking
description: Deep links, universal links and app links in React Native. Use when links do not open the app, for apple-app-site-association or assetlinks.json, lost cold-start links, expo-router linking, custom URL schemes.
user-invocable: false

Deep links that open the app every time

Two layers, different jobs

  • **Custom schemes** (`myapp://...`): trivial to set up, work from inside your own emails and QR flows, but any app can claim a scheme and browsers treat them as second-class. Fallback layer.
  • **Universal links (iOS) / App links (Android)** (`https://yourdomain.com/...`): the real thing. They need server-side proof files and that is where every setup fails.

Ship both: schemes for internal redirects (OAuth return, payment return), https links for anything a user might tap outside the app.

iOS: the AASA file and Apple's CDN

The proof file lives at `https://yourdomain.com/.well-known/apple-app-site-association`: HTTPS, no redirects, JSON body, appID as `TEAMID.bundleid`. The entitlement side is `applinks:yourdomain.com` (Expo: `associatedDomains` in app config).

The part that costs people days: **devices do not fetch your file directly**. Apple's CDN fetches it (first pickup within about a day) and caches it, so an AASA fix is not live when your server says it is. For development there is an alternate mode: entitlement `applinks:yourdomain.com?mode=developer` plus the device's developer mode skips the CDN. Use it for iteration, remove it for release builds.

If links still open Safari instead of the app: long-press the link and check for the "Open in app" option, confirm the file is reachable with `curl -i` (status 200, no redirect chain), and remember that pasting a URL into Safari's address bar deliberately does NOT trigger universal links; tap one from Notes or Messages instead.

Android: assetlinks.json and the signing key trap

The proof file lives at `https://yourdomain.com/.well-known/assetlinks.json` with the app's package name and the SHA-256 fingerprint of the **certificate that signs what ships**. The trap: with Play App Signing (the norm), Google re-signs your app, so the fingerprint must come from Play Console's App integrity page (the app signing key), NOT from your upload key or local keystore. A local-keystore fingerprint makes verification fail silently and only in production, which is the worst combination.

Add fingerprints additively: dev builds and store builds can both live in the file. Intent filters need `android:autoVerify="true"` on the https filter. Check verification state on a device with:

adb shell pm get-app-links your.package.name

`verified` next to the domain is the goal; anything else names the failing domain.

Cold start vs warm start, the classic lost link

A link that launches the app arrives through the initial-URL path; a link tapped while the app runs arrives through the URL event. Handling only the event listener means cold-start links vanish, and that is the most common deep-link bug report. expo-router handles both when the scheme and domains are configured, but anything custom (auth guards, splash gates, onboarding redirects) must re-apply the pending link AFTER the gate resolves, or the user lands on the home screen and the link dies.

Test both paths explicitly: once with the app killed, once with it backgrounded.

Testing without a website deploy

# custom scheme, both platforms
npx uri-scheme open "myapp://lesson/42" --ios
npx uri-scheme open "myapp://lesson/42" --android

# https links on Android without waiting for verification
adb shell am start -a android.intent.action.VIEW -d "https://yourdomain.com/lesson/42" your.package.name

iOS https links cannot be faked from the terminal in the same way; that is what `mode=developer` exists for.

Adjacent traps

  • OAuth and payment providers redirect back via your scheme or https link; a changed scheme breaks sign-in in the field, so treat the scheme as API surface and never rename it casually.
  • Deferred deep links (install, then first open lands on content) are not a platform feature; they require an attribution/linking service. Do not promise the product team otherwise.
  • Every route a link can open must handle "referenced thing is gone" gracefully; links outlive content.
Read more
Ships withclaude-code-react-native

Claude knows React. It doesn't know why your pod install just failed. A Claude Code plugin that adds the React Native knowledge you only get from shipping apps: build failure triage, SDK upgrades that don't eat a weekend, Hermes crash decoding, push

Get the whole plugin

Other skills on claude-code-react-native.