Mobile — Expo Development
Expo Router Deep Linking for Consumer Apps
Direct answer
Expo Router gives every screen a URL automatically — file-based routes double as deep link destinations, so myapp://product/42 opens app/product/[id].tsx with no manual link configuration. Set scheme in app.json for custom-scheme links, then add iOS Universal Links (associatedDomains + an AASA file) and Android App Links (autoVerify intent filters + assetlinks.json) so real https links from email, SMS, and search open your app directly.
Deep linking is where consumer apps win or lose re-engagement: a push notification, shared link, or marketing email should land the user on the exact screen, logged in, with no detours. Expo Router makes the routing half automatic — this guide covers the full production setup, including the domain-verification files both platforms require and the auth-redirect pattern for gated content.
Key facts, with sources
- 71% of State of React Native 2024 respondents reported using Expo's EAS Build, ahead of manual builds with Xcode (59.7%) and Android Studio (54.5%). (InfoQ)
- Expo SDK 52, released November 12, 2024, enabled the New Architecture by default for all new projects, and Expo Go for SDK 52 and higher supports only the New Architecture. (Expo Changelog)
- The official React Native documentation now recommends starting new apps with a framework, naming Expo, rather than initializing a bare project. (React Native documentation)
- EAS build caching can speed up subsequent Android and iOS builds by up to 30% and is available to all users at no additional cost as of the SDK 57 cycle. (Expo Changelog)
- Doctolib runs Expo tooling without EAS to scale developer experience on a healthcare app serving 90 million users, showing Expo's open-source tools work independently of its paid cloud services. (Doctolib Engineering (Medium))
Deep links are free with file-based routing
Because Expo Router derives navigation from the file system, every file under app/ already has a URL. app/product/[id].tsx answers product/42; app/(tabs)/profile.tsx answers /profile. The only setup for custom-scheme links is declaring the scheme, after which myapp://product/42 opens the right screen from a cold start, background, or foreground — Expo Router handles all three launch states without listener code.
{
"expo": {
"name": "MyApp",
"scheme": "myapp",
"ios": {
"associatedDomains": ["applinks:example.com"]
},
"android": {
"intentFilters": [
{
"action": "VIEW",
"autoVerify": true,
"data": [{ "scheme": "https", "host": "example.com", "pathPrefix": "/" }],
"category": ["BROWSABLE", "DEFAULT"]
}
]
}
}
}Custom scheme vs universal links: you need both
Custom-scheme links (myapp://) are reliable for QR codes, OAuth redirects, and links you fully control — but they do nothing if the app isn't installed, and messaging apps often refuse to make them tappable. Universal Links (iOS) and App Links (Android) use your real https URLs: if the app is installed the link opens it; if not, the same URL opens your website. For anything user-facing — emails, push campaigns, shared content — https links are the only serious option, and the website fallback doubles as your install funnel.
iOS: the apple-app-site-association file
iOS verifies domain ownership by fetching a JSON file from your site. Serve it at https://example.com/.well-known/apple-app-site-association — no file extension, served as application/json, no redirects. The appID is your Team ID plus bundle identifier. iOS caches this file on install, so changes take effect on reinstall, not immediately.
{
"applinks": {
"details": [
{
"appIDs": ["TEAMID1234.com.example.myapp"],
"components": [
{ "/": "/product/*" },
{ "/": "/invite/*" }
]
}
]
}
}Android: assetlinks.json and autoVerify
Android's equivalent lives at https://example.com/.well-known/assetlinks.json and pins your app's signing certificate fingerprint. Use the SHA-256 of the key that actually signs your store builds — for EAS-managed credentials, eas credentials prints it. With autoVerify: true in the intent filter (set in app.json above), Android verifies the domain at install time and opens your links without the app-chooser dialog.
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.myapp",
"sha256_cert_fingerprints": [
"AA:BB:CC:...:99"
]
}
}
]Reading params and gating links behind auth
Inside the destination screen, useLocalSearchParams reads the dynamic segment. The pattern that separates polished apps: when a deep link points at protected content and the user isn't logged in, save the intended path, route through login, then replay it — the user ends up where the link promised, not dumped on a home screen.
import { Redirect, useLocalSearchParams, usePathname } from "expo-router";
export default function ProductScreen() {
const { id } = useLocalSearchParams<{ id: string }>();
const pathname = usePathname();
const { user } = useAuth();
if (!user) {
// Send the intended destination along; login replays it on success
return <Redirect href={{ pathname: "/login", params: { next: pathname } }} />;
}
return <ProductDetails productId={id} />;
}
// In the login screen, after a successful sign-in:
// const { next } = useLocalSearchParams<{ next?: string }>();
// router.replace(next ?? "/");Testing deep links before shipping
Custom schemes can be exercised locally with npx uri-scheme open "myapp://product/42" --ios (or --android), or with adb shell am start commands on Android. Two caveats catch nearly everyone: Expo Go can't test your custom scheme or associated domains — it has its own scheme — so verification requires a development build via EAS; and both platforms cache the domain-verification files, so test Universal/App Links with a fresh install after the .well-known files are live in production.
When to hire senior help
Senior help pays off when deciding between managed and prebuild workflows, setting up EAS-based CI/CD and OTA update channels, or untangling a project that was ejected prematurely. An experienced Expo engineer can usually configure build, submit, and update pipelines in days, a task that costs first-time teams weeks of trial and error. If your stack includes React Native + Python + AI, a senior engineer who owns the full product beats coordinating multiple juniors.
Bottom line
Dhairya Senjaliya ships Mobile — Expo Development projects worldwide — book a scoping call to discuss your specific situation.
Common pitfalls to avoid
- ✕Ejecting to the bare workflow at the first native requirement instead of using config plugins and development builds, permanently giving up managed upgrades
- ✕Testing only in Expo Go and misconfiguring runtime versions for expo-updates, so an OTA update ships JavaScript that crashes against mismatched native binaries
- ✕Burning EAS build minutes on every commit in CI without caching or local builds, turning a convenience service into a surprise line item
- ✕Skipping several SDK upgrades in a row, then being forced into a large breaking migration when app store target API level deadlines arrive
Frequently asked questions
Do I need react-navigation linking config with Expo Router?
No — that's the point. Expo Router derives the URL map from your file structure automatically, so there is no manual linking configuration object to maintain. You only configure the scheme and domain associations in app.json.
Why do my Universal Links open Safari instead of the app?
Usually one of: the AASA file isn't reachable at /.well-known/apple-app-site-association over HTTPS without redirects, the appID doesn't match your Team ID + bundle ID, the path isn't covered by your components patterns, or iOS cached an old AASA — reinstall the app after fixing the file. Long-pressing the link in Notes shows whether iOS offers 'Open in app'.
Can I test deep links in Expo Go?
Not meaningfully for production behavior. Expo Go uses the exp:// scheme and can't claim your custom scheme or your domains. Create a development build (eas build --profile development) to test myapp:// links and Universal/App Links end to end.
Should we use Expo or plain React Native for a new app?
The React Native docs themselves now recommend starting with a framework, and Expo is the primary one; 71% of surveyed developers already build with EAS. Expo today supports custom native code through development builds and config plugins, so the old limitations that forced teams to avoid it mostly no longer apply.
Does Expo lock us into their platform?
No; expo prebuild can generate standard native iOS and Android projects at any time, and the open-source tooling works without Expo's paid EAS services, as demonstrated by Doctolib running Expo without EAS on an app with 90 million users. EAS Build, Submit, and Updates are optional conveniences, not requirements.
Is Expo production-ready for a serious commercial app?
Yes; Expo Go is only a development sandbox, while production apps ship as normal store binaries built with EAS or locally. Plan for the SDK release cadence of roughly three versions per year, since staying current is required to keep up with store policy and React Native releases.
Bottom line: Dhairya Senjaliya ships Mobile — Expo Development projects worldwide. Book a scoping call at https://dhairyasenjaliya.com/#book-call.