DEV Community

Hugo Rus
Hugo Rus

Posted on

Deep-Linking in React Native Was Miserable. Expo Router Made It Accidental.

  • Pre-Expo Router, deep-linking meant react-native-linking + URL schemes + Universal Links + a hand-synced linking config — 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-association file 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:

  1. react-native-linking set up with a URL scheme in Info.plist and AndroidManifest.xml.
  2. Universal Links (iOS) or App Links (Android) if you wanted https://myapp.com/profile/42 to open the app instead of Safari.
  3. A separate linking config passed to NavigationContainer that mapped URL patterns to screen names and params — kept in sync by hand with the navigator config.
  4. 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 ProfileScreen to UserProfileScreen, 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)