●MAX — Rork Max generates native Swift for iPhone, iPad, Apple Watch, Apple TV, and Vision Pro, with 2-click App Store publishing and no Xcode required●STACK — Standard Rork builds cross-platform mobile apps with React Native (Expo); choosing between the two by use case is the key decision●FOCUS — Unlike web-first tools such as Bolt or Lovable, Rork specializes in native iOS and Android app generation●BUGS — A hands-on review reports Rork resolved about 70% of bugs without manual help, with the remaining 30% needing edits in the exported codebase●FUNDING — Rork raised $2.8M from a16z (Andreessen Horowitz)●PRICING — It is free to start, with paid plans from $25/month, so you can try before committing●MAX — Rork Max generates native Swift for iPhone, iPad, Apple Watch, Apple TV, and Vision Pro, with 2-click App Store publishing and no Xcode required●STACK — Standard Rork builds cross-platform mobile apps with React Native (Expo); choosing between the two by use case is the key decision●FOCUS — Unlike web-first tools such as Bolt or Lovable, Rork specializes in native iOS and Android app generation●BUGS — A hands-on review reports Rork resolved about 70% of bugs without manual help, with the remaining 30% needing edits in the exported codebase●FUNDING — Rork raised $2.8M from a16z (Andreessen Horowitz)●PRICING — It is free to start, with paid plans from $25/month, so you can try before committing
Landing Users on the Right Screen Right After Install — Deferred Deep Links for Rork Apps
When someone follows a campaign link and installs through the store, the 'where did they come from' context is gone by launch time. Here is how to implement deferred deep linking in a Rork-built app without any third-party SDK.
Someone taps "Open in app" on a page showcasing a specific wallpaper theme, installs, launches — and lands on the plain home screen. They came for that theme, and now they have to hunt for it from scratch. When I started running campaigns as a solo developer, this was exactly what I was leaking.
The cause is simple. When a user taps a link but the app isn't installed, they get bounced to the App Store / Google Play first. The app installed and launched from the store no longer receives any "which link did they come from" context. The link's query parameters disappear the moment the store steps in between.
Deferred deep linking bridges that break. An SDK like Branch or AppsFlyer solves it in one shot, but those SDKs hoard a lot of personal data for attribution and carry a monthly cost. For both privacy and cost reasons, I chose to build a lightweight version myself. Here is that design, written for a Rork (Expo / React Native) app.
Start by sorting out "there are three paths"
Recovering a deferred deep link has three paths with different reliability. Conflating them makes the implementation needlessly complex.
Already-installed users (Universal Links / App Links work directly — most reliable)
iOS installs from the same browser (via clipboard — conditionally reliable)
Path 1 isn't deferred at all; it's a normal deep link, so I won't cover it here. The problem is paths 2 and 3. To carry information across the "break" that installation creates, you have to stash it temporarily outside the app — on a server or in the clipboard.
In my setup, iOS uses path 2 primarily with path 3 as a fallback. Android gets its path officially from Google Play's Install Referrer API, so I use that. The fact that the mechanisms differ entirely by platform is worth internalizing up front.
Stash a "fingerprint" on your server from the landing page
Without a third-party SDK, you record the moment-of-tap context on your own backend — only loose, non-identifying features (a fingerprint).
// landing.ts — call this the moment the landing page is tappedasync function recordClick(targetPath: string) { const fp = { target: targetPath, // the screen to land on (e.g. /theme/aurora) platform: /iphone|ipad/i.test(navigator.userAgent) ? "ios" : "other", lang: navigator.language, tzOffset: new Date().getTimezoneOffset(), screen: `${screen.width}x${screen.height}`, ts: Date.now(), }; // On iOS, also keep a copy in the clipboard (the primary path-2 mechanism) try { await navigator.clipboard.writeText(`rork-dl:${targetPath}`); } catch { // If clipboard is unavailable, defer to path-3 server matching } await fetch("https://api.example.com/dl/click", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(fp), }); location.href = "https://apps.apple.com/app/idXXXXXXXX"; // off to the store}
Writing to the clipboard is the highest-confidence clue on iOS — it's roughly equivalent to "a first launch coming from the same Safari session." But if the user overwrites the clipboard with another action, it's gone, so keep it as the primary mechanism only and run server matching alongside as insurance.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Why link context vanishes through the store, and how to choose among three recovery paths
✦Recovering the destination with your own backend + fingerprint matching, no third-party SDK
✦The matching window and confidence threshold that prevent wrong-screen landings
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
The app attempts recovery exactly once, on first launch. It checks the clipboard first, and if that whiffs, it queries the server's fingerprint matcher.
// resolveDeferredLink.tsimport * as Clipboard from "expo-clipboard";import { router } from "expo-router";const FIRST_RUN_KEY = "dl_resolved_v1";export async function resolveDeferredLink(storage: Storage) { // Run only on first launch (so later launches don't absorb someone else's link) if (await storage.getItem(FIRST_RUN_KEY)) return; await storage.setItem(FIRST_RUN_KEY, "1"); // Path 2: clipboard const clip = await Clipboard.getStringAsync(); const m = clip.match(/^rork-dl:(\/\S+)$/); if (m) { await Clipboard.setStringAsync(""); // clear the copy once it's used router.replace(m[1]); return; } // Path 3: server fingerprint matching const target = await matchFingerprint(); if (target) router.replace(target);}async function matchFingerprint(): Promise<string | null> { const res = await fetch("https://api.example.com/dl/match", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(currentFingerprint()), }); const { target, confidence } = await res.json(); // Don't accept low-confidence matches (a wrong landing hurts the experience) return confidence >= 0.7 ? target : null;}
Using router.replace here rather than push is subtle but important. Instead of stacking the destination on top of the home screen, you replace the initial landing itself, which keeps the back-button behavior natural. I first built it with push and created a glitch where backing out of the destination flashed an empty home screen for an instant.
The threshold and window that prevent wrong matches
Path-3 fingerprint matching is probabilistic, so there's always a risk of "absorbing someone else's link by mistake." The design that suppresses this is what determines quality. The three rules I run with:
Keep the matching window short. Records older than 1 hour from click to first launch are dropped from matching. The median time from ad click to install completion was within a few minutes for my apps, so an hour was plenty.
Produce a confidence score from the number of matching features and reject anything under 0.7. Weight platform, language, timezone, and screen resolution agreement, and throw away merely vaguely-similar candidates.
Mark a click record as consumed the instant it's matched. Even if multiple launches hit the same fingerprint, only the first recovers; the rest fall back to a normal home landing.
A wrong landing breeds the distrust of "you took me somewhere I never asked for," so when in doubt, don't recover. I initially set the confidence threshold at 0.5, took one complaint about landing on an unrelated screen, and raised it to 0.7. With probabilistic methods, designing as if the cost of a false positive is high turns out to be the shortcut.
Where to draw the build-it-yourself line
Honestly, once you're running ads at scale and need rigorous attribution, a dedicated SDK is easier to operate. A homegrown implementation means keeping up with OS changes to clipboard APIs and degrading fingerprint precision yourself.
Even so, in the early indie phase — up to maybe tens of thousands of installs a month — this lightweight design covers the minimum: landing campaign visitors on the right screen. Keep a setup that stashes no personal data externally and carries no fixed cost as your first tier. Then consider an SDK once your scale comes into focus. That order suited me; I hope it gives anyone weighing the same decision something to go on.
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.