RORK LABJP
TOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the roundTOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the round
Articles/Dev Tools
Dev Tools/2026-06-09Intermediate

Deep Links in Rork Max — Universal Links and URL Schemes

A hands-on guide to deep linking in Rork Max apps: when to use URL Schemes vs. Universal Links, the AASA/assetlinks pitfalls, and the cold-start trap — with working examples.

rork58deep-linksuniversal-linksreact-native12tutorial20

Carry the user to the right screen in one tap

You send a push notification, the user taps it — and lands on the home screen. The message said "Today's wallpaper is ready," but the wallpaper itself is still several taps away, and most people give up before they get there. Running wallpaper apps, this was a leak I watched happen far too often.

Deep links close that distance to zero. Tapping a URL like myapp://wallpaper/sky-blue launches the app and opens that wallpaper's detail screen directly. Whether the entry point is a notification, a website, another app, or a tracked marketing link, a deep link carries the user straight to the one screen you meant.

This guide walks through implementing both URL Schemes and Universal Links in a Rork Max (Expo-based) app, and the snags you only discover on a real device.

URL Schemes vs. Universal Links — which, and when

The two methods play different roles. A URL Scheme (myapp://path) is the easiest to set up and is ideal for in-app testing and inter-app calls. The catch: if the app isn't installed, the tap does nothing, and to the user the link simply looks broken.

Universal Links (iOS) and App Links (Android) both use ordinary https://myapp.com/path web URLs. If the app is installed, the URL opens the app; if not, it opens the web page instead. That seamless fallback is the whole point, and it's the approach Apple officially recommends. For any URL you hand to users — notifications, campaigns — I default to Universal Links / App Links.

In practice the split is simple: use URL Schemes for internal flows and debugging, and Universal Links for anything a user will tap. Keep both wired up and you rarely have to think about it.

Declare your scheme and associated domains in app.config

Projects generated by Rork Max are Expo-based, so you declare your entry points in app.json (or app.config.ts).

{
  "expo": {
    "scheme": "myapp",
    "ios": {
      "associatedDomains": ["applinks:myapp.com"]
    },
    "android": {
      "intentFilters": [
        {
          "action": "VIEW",
          "autoVerify": true,
          "data": [
            { "scheme": "https", "host": "myapp.com", "pathPrefix": "/wallpaper" }
          ],
          "category": ["BROWSABLE", "DEFAULT"]
        }
      ]
    }
  }
}

scheme registers the URL Scheme (myapp://), while associatedDomains and intentFilters declare your Universal Links / App Links. So far this is all config-file work, and almost nobody trips here. The real problems live on the server side.

Where to look first when Universal Links "don't work"

"I added every setting, but tapping the link still opens Safari." That's the most common Universal Links symptom, and the cause is almost always the server-side verification file, not your app code.

iOS fetches a file at https://myapp.com/.well-known/apple-app-site-association (the AASA) for each declared domain, and only opens the app once the contents match. Three failures dominate: the file isn't served under /.well-known/, the Content-Type isn't application/json, or there's a redirect (301/302) in the way. Any one of those and iOS quietly gives up and opens the URL as a plain web link in Safari.

A correct AASA returns this JSON with a clean 200 and no redirect:

{
  "applinks": {
    "details": [
      {
        "appIDs": ["YOUR_TEAM_ID.com.example.myapp"],
        "components": [
          { "/": "/wallpaper/*", "comment": "links into wallpaper detail" }
        ]
      }
    ]
  }
}

Android reads https://myapp.com/.well-known/assetlinks.json:

[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.example.myapp",
      "sha256_cert_fingerprints": ["YOUR_SHA256_FINGERPRINT"]
    }
  }
]

The line that caught me out was sha256_cert_fingerprints. If you ship a production AAB while leaving your local debug certificate's fingerprint in place, it won't match the Play signing key and App Links never verify. For production, use the SHA-256 shown under "App signing" in the Play Console — an unglamorous detail that matters. After deploying either file, I always open its URL in a browser to confirm it returns raw JSON with no redirect.

React Navigation integration and the cold-start trap

Once the entry points resolve, you map URLs to screens. You can ask Rork Max in plain language:

"Add these deep links to the app:
- myapp://home → Home screen
- myapp://wallpaper/:id → Wallpaper Detail screen
- myapp://profile → Profile screen
Implement it using React Navigation's linking config."

The generated linking config looks roughly like this:

const linking = {
  prefixes: ['myapp://', 'https://myapp.com'],
  config: {
    screens: {
      Home: 'home',
      WallpaperDetail: 'wallpaper/:id',
      Profile: 'profile',
    },
  },
};
 
<NavigationContainer linking={linking}>
  {/* ... */}
</NavigationContainer>

Here's the trap you only feel on a device. There are two paths for receiving a link: when the app is already running (warm), Linking.addEventListener('url', ...) fires; but when the app launches from a fully terminated (cold) state, you'll miss the first URL unless you also read it with Linking.getInitialURL().

That's exactly what bit me in a wallpaper app. I'd written only addEventListener by hand, so tapping the notification while the app was killed landed on the home screen every time — never the wallpaper screen I intended.

// Before: only catches warm launches, misses cold start
import * as Linking from 'expo-linking';
 
useEffect(() => {
  const sub = Linking.addEventListener('url', ({ url }) => handleUrl(url));
  return () => sub.remove();
}, []);
// After: explicitly read the launch URL too
import * as Linking from 'expo-linking';
 
useEffect(() => {
  // Cold start: the URL the app was opened with
  Linking.getInitialURL().then((url) => {
    if (url) handleUrl(url);
  });
  // Warm: navigation while running
  const sub = Linking.addEventListener('url', ({ url }) => handleUrl(url));
  return () => sub.remove();
}, []);

The good news: if you use React Navigation's linking config, it reads getInitialURL internally, so both paths are handled for you. The flip side — you only need to catch the cold start yourself when you bypass the linking config and handle URLs manually. Keep that distinction in mind and the confusion disappears.

Route from a push notification to a specific screen

Finally, let's fix the "tap the notification, land on home" problem from the opening. Put the deep link URL in the notification's data payload, then open it on tap.

// Server side: include the destination URL in the notification data
const notification = {
  title: "Today's wallpaper is ready",
  body: "Added a new sky gradient",
  data: { url: "myapp://wallpaper/sky-blue" }
};
 
// App side: open the URL on tap
import * as Notifications from 'expo-notifications';
 
Notifications.addNotificationResponseReceivedListener((response) => {
  const url = response.notification.request.content.data?.url;
  if (url) Linking.openURL(url);
});

Calling Linking.openURL hands the URL to the linking config you defined, which carries it all the way to the wallpaper detail screen. After years of running small apps, I've come to believe that where the tap lands matters more for retention than the open rate itself.

Your next step

Start with just the URL Scheme: run npx uri-scheme open "myapp://wallpaper/sky-blue" --ios from your terminal against the simulator and confirm it lands on the screen you intended. Once that path works, wiring up AASA / assetlinks for Universal Links is a short, continuous step from there.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Dev Tools2026-05-24
Why your Rork app shows a blank screen or loses state after returning from background
Your Rork app sits in the background overnight, you tap the icon the next morning, and the screen is blank — or your drafts have disappeared, or you have been silently logged out. iOS process reclamation, AsyncStorage rehydration timing, and Navigation state restoration each cause a different version of this bug. Notes from running wallpaper and wellness apps with 50M downloads as an indie developer.
Dev Tools2026-05-23
Rork-Specific 'expo start --offline forbidden': Four Causes in Rork's Template Config
When expo start --offline returns 'forbidden' specifically on a Rork-generated project, the cause is usually Rork template config: tsconfigPaths, an un-generated expo-router cache, native prebuild, or a lockfile mismatch. Four Rork-specific fixes; the generic Expo proxy and dependency-validation guide is covered separately.
Dev Tools2026-05-20
Fixing 0x8badf00d Watchdog Kills That Wipe Out Rork Apps at Launch
Your Rork iOS app dies right after launch on real devices. Crashlytics shows exception code 0x8badf00d. Here is the watchdog termination story and the exact steps an indie developer running React Native apps for 50M downloads uses to make it stop.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →