- Pre-Expo Router, deep-linking meant
react-native-linking+ URL schemes + Universal Links + a hand-syncedlinkingconfig — roughly two weeks of work that broke on every screen rename. - Three things reliably broke in prod: renamed screens (silent 404s), missing params (crashes), and state mismatch (app resets to home instead of stacking).
- Expo Router fixes this by making the URL structure be the file structure — there's no separate config to desync.
- One gotcha remains: Universal Links still need an
apple-app-site-associationfile and Xcode entitlements. That part's on Apple, not the framework.
Deep-linking used to be the feature that dev teams promised at kickoff and quietly cut before launch. In three separate React Native apps I've shipped, the deep-linking ticket got moved to "next quarter" at least twice before someone finally forced it.
Expo Router changed that — not because it's easier (though it is), but because deep-linking is now the default, not an add-on.
The 2022 baseline
Getting a link like myapp://profile/42 to open the profile screen for user 42 used to require:
-
react-native-linkingset up with a URL scheme inInfo.plistandAndroidManifest.xml. - Universal Links (iOS) or App Links (Android) if you wanted
https://myapp.com/profile/42to open the app instead of Safari. - A separate
linkingconfig passed toNavigationContainerthat mapped URL patterns to screen names and params — kept in sync by hand with the navigator config. - Deep-link testing across cold-start, warm-start, and background-resume paths, each of which broke differently.
By the time you had all four working, you'd spent two weeks and had a URL-to-screen mapping that broke every time someone renamed a screen.
The three things that used to break in prod
-
Renamed screens. Someone renames
ProfileScreentoUserProfileScreen, forgets to update the linking config, and deep links silently 404 to the home screen. -
Missing params.
myapp://profile(no id) crashes the screen because someone forgot the id fallback. - State mismatch. The user is deep in a Stack navigator when the link arrives; the app resets to home instead of stacking correctly.
What Expo Router does differently
Because the URL structure is the route file structure, you can't have a mismatch between what a URL says and what screens exist. myapp://profile/42 maps to app/profile/[id].tsx because that file exists. Rename the file and the URL structure changes with it. There's no separate config file to keep in sync.
That single property — one source of truth instead of two — eliminates the first failure mode entirely, and makes the other two far easier to reason about.
A support-team flow that just works
My team recently shipped a support-tools feature: our CS team can send customers a link like myapp://support/ticket/T-1234 from a Slack app.
Before Expo Router, that would have been a three-week ticket — design the URL schema, wire the linking config, test across cold-start states. With Expo Router, it was two files (app/support/ticket/[id].tsx and a Slack app integration) and a two-day build.
The link works whether the user has the app closed, in the background, or open on another screen.
The one remaining gotcha
Universal Links — the iOS feature that makes https://myapp.com/profile/42 open your app instead of Safari — still needs an apple-app-site-association file served from your website, plus the right entitlements in Xcode. Expo Router doesn't automate this; it's an Apple-side concern. Set aside half a day for it when you first turn Universal Links on.
Takeaway
Deep-linking used to be a feature you promised in month 1 and delivered in month 4. In an Expo Router app it's a feature you get in month 0 by not opting out.
If your team is still hand-rolling deep-link config in 2026, you're solving a problem the framework already solved.
What's the deep-link bug that cost you the most time — a silent 404, a cold-start crash, or Universal Links entitlements? Drop it in the comments.
Top comments (0)