Why Deep Linking Matters More Than Ever in 2026
Deep linking is the mechanism that lets URLs open specific screens inside your mobile app instead of dumping users on a generic home page. It powers everything from marketing campaigns and push notification taps to password reset flows and social media sharing. Without it, every external reference to your app just drops users at the front door and makes them find their own way — which, honestly, most of them won't bother doing.
In 2026, deep linking has shifted from a nice-to-have to core infrastructure. With Firebase Dynamic Links officially shut down in August 2025 and Apple and Google tightening verification requirements for URL handling, the landscape has changed significantly. If you're building with Expo Router, the good news is that every file in your app/ directory is automatically a deep-linkable route. The challenge is configuring the native plumbing — Universal Links on iOS, App Links on Android, and the server-side verification files that tie them together.
This guide walks through the entire setup from scratch: custom URL schemes for development, verified Universal Links and App Links for production, server configuration, the +native-intent file for custom link handling, authentication-aware redirects, and troubleshooting the issues that trip up most developers. Every code example targets Expo SDK 55, React Native 0.83, and Expo Router v4.
Understanding the Three Types of Mobile Links
Before writing any code, it helps to understand the three distinct linking mechanisms available on mobile and when to use each one.
Custom URL Schemes
Custom URL schemes use a protocol like myapp:// to open your app. They're the simplest form of deep linking and require no server configuration. However, they have serious limitations: they fail silently if the app isn't installed, they can't redirect users to an app store, and any other app can register the same scheme, creating hijacking risks.
Use custom schemes primarily during development and for app-to-app communication where you control both sides.
iOS Universal Links
Universal Links use standard https:// URLs. When a user taps a Universal Link and your app is installed, iOS opens the app directly. If the app isn't installed, the URL loads in Safari like a normal web page — where you can show a landing page or redirect to the App Store. Apple verifies the association between your domain and your app through an Apple App Site Association (AASA) file hosted on your server.
Android App Links
App Links are the Android equivalent of Universal Links. They also use https:// URLs and open your app when it's installed. Android verifies the association through a Digital Asset Links file (assetlinks.json) hosted on your server. Unlike basic Android deep links, verified App Links skip the disambiguation dialog that asks users which app should handle the URL.
For production apps, you almost always want Universal Links and App Links. They provide a better user experience, work whether or not the app is installed, and are verified by the operating system so other apps can't intercept your URLs.
Setting Up Custom URL Schemes in Expo Router
Start with custom URL schemes because they require the least configuration and let you verify that your routing logic works before adding the complexity of server-side verification.
Configure the Scheme
Add a scheme property to your app.json:
{
"expo": {
"scheme": "myapp",
"plugins": ["expo-router"]
}
}
After changing the scheme, you need to create a new development build. The scheme is baked into the native binary and isn't available through Expo Go:
npx expo prebuild --clean
npx expo run:ios
# or
npx expo run:android
How Expo Router Maps Files to Links
Expo Router automatically generates routes from your file structure. With the scheme configured above, the following file structure produces these deep links:
app/
├── index.tsx → myapp:///
├── products/
│ ├── index.tsx → myapp:///products
│ └── [id].tsx → myapp:///products/42
├── settings.tsx → myapp:///settings
└── profile/
└── [username].tsx → myapp:///profile/johndoe
No linking configuration file needed. No manual route mapping. Each file is a route, and each route is a deep link. It's one of those things about Expo Router that genuinely delights you once you realize how much boilerplate you're not writing.
Testing Custom Scheme Links
With a development build running, test from the terminal:
# iOS Simulator
npx uri-scheme open "myapp:///products/42" --ios
# Android Emulator
adb shell am start -a android.intent.action.VIEW -d "myapp:///products/42"
# Or use Expo CLI
npx expo start --dev-client
# Then open: myapp:///products/42 from another app or browser
If these links work, your routing is correctly configured. The next step is adding verified links for production.
Configuring iOS Universal Links
Universal Links require three things: an entitlement in your app that declares which domain it handles, an AASA file on your server that declares which app handles its URLs, and HTTPS on your domain.
Step 1: Add Associated Domains to app.json
Add the associatedDomains array to your iOS configuration:
{
"expo": {
"scheme": "myapp",
"ios": {
"bundleIdentifier": "com.yourcompany.myapp",
"associatedDomains": [
"applinks:yourapp.com",
"applinks:www.yourapp.com"
]
}
}
}
The applinks: prefix is required — it tells iOS that your app can handle links from these domains. If you use EAS Build, the entitlement is automatically registered with Apple during the build process.
Step 2: Create the AASA File
Create the Apple App Site Association file and place it at public/.well-known/apple-app-site-association on your web server. If you're using Expo Router for your website, place it at that path in your project. The file must be served without a file extension and with the content type application/json:
{
"applinks": {
"apps": [],
"details": [
{
"appIDs": [
"TEAM_ID.com.yourcompany.myapp"
],
"components": [
{
"/": "/products/*",
"comment": "Match all product pages"
},
{
"/": "/profile/*",
"comment": "Match all profile pages"
},
{
"/": "/*",
"exclude": true,
"comment": "Exclude everything else by default"
}
]
}
]
}
}
Replace TEAM_ID with your Apple Developer Team ID and com.yourcompany.myapp with your bundle identifier. The components array uses the newer AASA format (supported on iOS 13+) which gives you finer control over which paths your app handles.
If you want your app to handle all paths, simplify the components to a single wildcard rule:
{
"applinks": {
"apps": [],
"details": [
{
"appIDs": ["TEAM_ID.com.yourcompany.myapp"],
"components": [
{
"/": "/*"
}
]
}
]
}
}
Step 3: Verify the AASA File
The file must be accessible at https://yourapp.com/.well-known/apple-app-site-association. Verify it's correctly served:
curl -I https://yourapp.com/.well-known/apple-app-site-association
Check that the response has a 200 status code and the Content-Type header is application/json. Some hosting providers add redirects or file extensions that break verification — this is a surprisingly common gotcha. If your host requires extensions, you may need to create both apple-app-site-association and apple-app-site-association.json and set up a redirect.
Apple also provides a validation tool. Search for the Apple AASA Validator or use the endpoint at https://app-site-association.cdn-apple.com/a/v1/yourapp.com to see what Apple has cached for your domain.
Important AASA Constraints
- The file must be under 128 KB uncompressed
- Your domain must use HTTPS with a valid certificate
- Apple's CDN caches the AASA file and refreshes it approximately once per week after initial download, which happens within 24 hours of app installation
- Data stored with
expo-secure-storepersists across reinstalls on iOS due to Keychain behavior — keep this in mind when testing link flows that involve authentication tokens
Configuring Android App Links
Android App Links follow a similar pattern: declare intent filters in your app configuration and host a verification file on your server.
Step 1: Add Intent Filters to app.json
Configure intentFilters in your Android configuration with autoVerify: true:
{
"expo": {
"scheme": "myapp",
"android": {
"package": "com.yourcompany.myapp",
"intentFilters": [
{
"action": "VIEW",
"autoVerify": true,
"data": [
{
"scheme": "https",
"host": "yourapp.com",
"pathPrefix": "/"
}
],
"category": ["BROWSABLE", "DEFAULT"]
}
]
}
}
}
The autoVerify: true flag is critical. Without it, Android treats these as basic deep links that show a disambiguation dialog asking the user which app should handle the URL. With verification, your app opens directly.
Step 2: Create the Digital Asset Links File
Create assetlinks.json at public/.well-known/assetlinks.json on your web server:
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.yourcompany.myapp",
"sha256_cert_fingerprints": [
"14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"
]
}
}
]
Step 3: Get Your SHA-256 Fingerprint
The SHA-256 fingerprint must match the certificate used to sign your app. How you get it depends on your build setup:
# If using EAS Build
eas credentials -p android
# Select your build profile, then copy the SHA256 Fingerprint
# If using a local keystore
keytool -list -v -keystore ./android/app/my-upload-key.keystore \
-alias my-key-alias
An important gotcha: Google Play may use a different signing key than your upload key. If you use Google Play App Signing, include both your upload key fingerprint (for testing) and the production signing key fingerprint from the Google Play Console. Add both to the sha256_cert_fingerprints array:
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.yourcompany.myapp",
"sha256_cert_fingerprints": [
"YOUR_UPLOAD_KEY_FINGERPRINT",
"YOUR_PLAY_STORE_SIGNING_KEY_FINGERPRINT"
]
}
}
]
Step 4: Verify the Asset Links File
Confirm the file is accessible at https://yourapp.com/.well-known/assetlinks.json. You can also verify using Google's Digital Asset Links API:
https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://yourapp.com&relation=delegate_permission/common.handle_all_urls
In the Google Play Console, navigate to your app, then go to Deep Links to check whether the domain verification is successful.
Complete app.json Configuration
Here's a full app.json configuration combining custom schemes, Universal Links, and App Links:
{
"expo": {
"name": "My App",
"slug": "my-app",
"scheme": "myapp",
"plugins": ["expo-router"],
"ios": {
"bundleIdentifier": "com.yourcompany.myapp",
"associatedDomains": [
"applinks:yourapp.com",
"applinks:www.yourapp.com"
]
},
"android": {
"package": "com.yourcompany.myapp",
"intentFilters": [
{
"action": "VIEW",
"autoVerify": true,
"data": [
{
"scheme": "https",
"host": "yourapp.com",
"pathPrefix": "/"
},
{
"scheme": "https",
"host": "www.yourapp.com",
"pathPrefix": "/"
}
],
"category": ["BROWSABLE", "DEFAULT"]
}
]
}
}
}
After updating your app config, rebuild your app with eas build or npx expo prebuild --clean followed by a native run command. Changes to associated domains and intent filters require a fresh native build — you can't hot reload your way out of this one.
Handling Incoming Links with expo-linking
In most cases, Expo Router handles incoming links automatically by matching them to your file-based routes. However, you may need to programmatically read or react to incoming URLs — for analytics, conditional navigation, or integrating third-party link services.
The useLinkingURL Hook
The recommended way to observe incoming URLs is with the useLinkingURL hook from expo-linking. Note that the older useURL hook is deprecated as of Expo SDK 55:
import * as Linking from "expo-linking";
import { useEffect } from "react";
import { router } from "expo-router";
export function useLinkTracker() {
const url = Linking.useLinkingURL();
useEffect(() => {
if (url) {
const parsed = Linking.parse(url);
console.log("Incoming link:", {
path: parsed.path,
hostname: parsed.hostname,
queryParams: parsed.queryParams,
});
// Track deep link in analytics
analytics.track("deep_link_opened", {
path: parsed.path,
source: parsed.queryParams?.utm_source,
});
}
}, [url]);
}
The useLinkingURL hook handles both scenarios: it returns the initial URL that launched the app (cold start) and observes new URLs received while the app is already open (warm start).
Parsing URLs with Linking.parse
The Linking.parse() method extracts structured data from any URL format — custom schemes, Universal Links, and Expo Go development URLs:
import * as Linking from "expo-linking";
// Custom scheme
Linking.parse("myapp:///products/42?ref=share");
// → { path: "products/42", hostname: null, queryParams: { ref: "share" } }
// Universal Link
Linking.parse("https://yourapp.com/products/42?ref=share");
// → { path: "products/42", hostname: "yourapp.com", queryParams: { ref: "share" } }
// Expo Go (development)
Linking.parse("exp://192.168.1.5:8081/--/products/42");
// → { path: "products/42", hostname: "192.168.1.5", queryParams: {} }
Creating URLs for Sharing
When building share functionality, use Linking.createURL to generate URLs that respect the current environment:
import * as Linking from "expo-linking";
// In development (Expo Dev Client)
const url = Linking.createURL("/products/42", {
queryParams: { ref: "share" },
});
// → exp://192.168.1.5:8081/--/products/42?ref=share
// In production
// → myapp:///products/42?ref=share
For share links you want to work universally (whether the app is installed or not), construct your verified domain URL directly instead of using createURL:
const shareUrl = `https://yourapp.com/products/${productId}?ref=share`;
Custom Link Handling with +native-intent
Sometimes you need to transform or redirect incoming links before Expo Router processes them. Third-party services may send links in formats that don't match your route structure, or you may need to resolve shortened URLs. The +native-intent file handles this.
Creating the +native-intent File
Create app/+native-intent.tsx at the root of your app directory. This file exports a redirectSystemPath function that receives every incoming URL before the router processes it:
// app/+native-intent.tsx
import { router } from "expo-router";
export function redirectSystemPath({
path,
initial,
}: {
path: string;
initial: boolean;
}) {
// Handle legacy URL formats
if (path.includes("/item/")) {
// Rewrite /item/123 to /products/123
const id = path.split("/item/")[1]?.split("?")[0];
return `/products/${id}`;
}
// Handle marketing campaign links
if (path.includes("/go/")) {
const campaign = path.split("/go/")[1];
switch (campaign) {
case "premium":
return "/settings/subscription";
case "onboarding":
return "/onboarding";
default:
return "/";
}
}
// Pass through all other links unchanged
return path;
}
The initial parameter tells you whether this is the link that launched the app (true) or a link received while the app was already open (false). This distinction matters for links that should only trigger navigation when the app launches cold.
Limitations of +native-intent
The +native-intent file runs outside your React component tree. This means:
- No access to React context, hooks, or state
- No access to authentication status
- Only works on native platforms (iOS and Android), not web
- Must return synchronously — no async operations
If you need authentication-aware link handling, use the redirect pattern described in the next section instead.
Authentication-Aware Deep Links
A common requirement is that deep links should respect authentication state. If a user taps a link to /orders/456 but isn't logged in, they should be redirected to the sign-in screen and then forwarded to the original destination after authenticating.
Protected Route Layout
Expo Router handles this through redirect logic in layout files. Create a layout that checks authentication status and redirects unauthenticated users:
// app/(protected)/_layout.tsx
import { Redirect, Stack } from "expo-router";
import { useAuth } from "@/hooks/useAuth";
export default function ProtectedLayout() {
const { isAuthenticated, isLoading } = useAuth();
if (isLoading) {
return null; // Or a loading spinner
}
if (!isAuthenticated) {
return <Redirect href="/sign-in" />;
}
return <Stack />;
}
Preserving the Deep Link Destination
To redirect users back to their intended destination after sign-in, pass the original URL as a parameter:
// app/(protected)/_layout.tsx
import { Redirect, Stack, usePathname } from "expo-router";
import { useAuth } from "@/hooks/useAuth";
export default function ProtectedLayout() {
const { isAuthenticated, isLoading } = useAuth();
const pathname = usePathname();
if (isLoading) {
return null;
}
if (!isAuthenticated) {
return (
<Redirect
href={`/sign-in?returnTo=${encodeURIComponent(pathname)}`}
/>
);
}
return <Stack />;
}
// app/sign-in.tsx
import { router, useLocalSearchParams } from "expo-router";
export default function SignIn() {
const { returnTo } = useLocalSearchParams<{ returnTo?: string }>();
async function handleSignIn() {
const success = await signIn(/* credentials */);
if (success) {
router.replace(returnTo ?? "/");
}
}
// ... render sign-in form
}
This pattern works seamlessly with deep links. A user who taps https://yourapp.com/orders/456 will see the sign-in screen, authenticate, and then land on the orders screen — exactly where the link intended them to go.
Deferred Deep Linking: When the App Is Not Installed
One major gap that custom URL schemes can't solve is what happens when the user doesn't have your app installed. Universal Links and App Links partially solve this by falling back to your website, but if you want to carry the intended destination through an app install, you need deferred deep linking.
The Firebase Dynamic Links Shutdown
Firebase Dynamic Links, which was the most popular free solution for deferred deep linking, was shut down in August 2025. All .page.link domains now return HTTP 404 errors. If you were relying on Firebase Dynamic Links, you need to migrate.
Current Alternatives
The recommended alternatives for deferred deep linking in 2026 are:
- Branch — The most popular dedicated deep linking platform. Offers deferred deep linking, attribution, and analytics. Has an official React Native SDK.
- Adjust — Focused on attribution and analytics with deep linking built in.
- AppsFlyer — Similar to Adjust, with strong deep linking and attribution capabilities.
- Custom implementation — For simpler needs, your website can detect whether the app is installed via Universal Links and store the intended destination in a cookie or server session. After the user installs and opens the app, an API call retrieves the stored destination.
Integrating Branch with Expo Router
Branch integrates with Expo Router through the +native-intent file or a custom hook in your layout. A common pattern is to use a deep link observer hook inside your authenticated layout:
// hooks/useBranchLinks.ts
import { useEffect } from "react";
import branch from "react-native-branch";
import { router } from "expo-router";
export function useBranchLinks() {
useEffect(() => {
const unsubscribe = branch.subscribe({
onOpenComplete: ({ params, error }) => {
if (error) {
console.error("Branch error:", error);
return;
}
if (params?.["+non_branch_link"]) {
// Handle non-Branch links normally
return;
}
if (params?.productId) {
router.push(`/products/${params.productId}`);
} else if (params?.screen) {
router.push(params.screen);
}
},
});
return () => unsubscribe();
}, []);
}
// Usage in app/(protected)/_layout.tsx
export default function ProtectedLayout() {
useBranchLinks();
// ... rest of layout
}
Placing the hook inside the authenticated layout ensures that Branch links are only processed after the user is signed in, avoiding race conditions between authentication and deep link navigation.
Testing Deep Links on Real Devices
Testing deep links properly requires running on actual devices or correctly configured simulators. Here's a systematic approach.
Testing Custom Schemes
# iOS Simulator
xcrun simctl openurl booted "myapp:///products/42"
# Android Emulator
adb shell am start -a android.intent.action.VIEW \
-d "myapp:///products/42" com.yourcompany.myapp
Testing Universal Links (iOS)
Universal Links have a specific quirk on iOS: tapping a link in Safari on the same domain won't open the app. iOS only triggers Universal Links when the user navigates from a different domain. To test:
- Send yourself a link via iMessage or Notes
- Tap the link — it should open your app
- Long-press the link to verify the "Open in App Name" option appears
For local development, use the Expo tunnel feature:
# Set a consistent tunnel subdomain
EXPO_TUNNEL_SUBDOMAIN=myapp-dev npx expo start --tunnel
Then configure your AASA to include the tunnel domain during development.
Testing App Links (Android)
# Verify app link status
adb shell pm get-app-links com.yourcompany.myapp
# Trigger verification manually
adb shell pm verify-app-links --re-verify com.yourcompany.myapp
# Test the link
adb shell am start -a android.intent.action.VIEW \
-d "https://yourapp.com/products/42" com.yourcompany.myapp
If the get-app-links command shows a status other than "verified," check your assetlinks.json file and ensure the SHA-256 fingerprints match.
Troubleshooting Common Deep Linking Issues
Deep linking involves coordination between your app, the operating system, and your web server. When something breaks, the root cause can be in any of these layers — which makes debugging it genuinely annoying. Here are the most common issues and their solutions.
Links Open in the Browser Instead of the App
iOS: Check that your AASA file is valid, accessible over HTTPS, and that the appIDs match your Team ID and bundle identifier exactly. Use Apple's CDN endpoint to verify what Apple has cached. Remember that AASA changes can take up to a week to propagate after the initial 24-hour window.
Android: Verify that autoVerify: true is set in your intent filters. Check that your assetlinks.json includes the correct package name and all relevant SHA-256 fingerprints (both upload and signing keys if using Google Play App Signing). Run adb shell pm verify-app-links --re-verify to force re-verification.
Links Work When the App Is Open but Not From Cold Start
This is a known issue in some Expo SDK versions. On a cold start, the initial URL may not be processed correctly. Ensure your root layout properly handles the loading state before rendering child routes. If you're using a splash screen, make sure it doesn't block URL processing:
// app/_layout.tsx
import * as SplashScreen from "expo-splash-screen";
import { useEffect, useState } from "react";
import { Slot } from "expo-router";
SplashScreen.preventAutoHideAsync();
export default function RootLayout() {
const [ready, setReady] = useState(false);
useEffect(() => {
async function prepare() {
// Load fonts, check auth, etc.
await loadResources();
setReady(true);
await SplashScreen.hideAsync();
}
prepare();
}, []);
if (!ready) {
return null; // Keep splash screen visible
}
return <Slot />;
}
Deep Links Work in Development but Not in Production
The most common cause is different signing certificates. Development builds use a debug certificate while production builds use your release certificate. Make sure your assetlinks.json includes the production signing key fingerprint from the Google Play Console (under Setup → App signing).
On iOS, verify that the entitlements are correctly included in your production build. Running eas build handles this automatically, but if you're building locally, double-check your entitlements file.
getInitialURL Returns Null
The getInitialURL method is deprecated in favor of the useLinkingURL hook. However, if you're still using it, note that it returns null when Remote JS Debugging is active. Disable the debugger and use the new architecture's built-in debugging tools instead.
Links Fail in Expo Go
Expo Go doesn't support custom URL schemes. It only supports exp:// links. To test deep links, create a development build with npx expo run:ios or npx expo run:android. The Linking.createURL function accounts for this difference automatically.
Server Configuration Examples
Different hosting platforms require different approaches for serving the verification files. Here are configurations for common setups.
Nginx
location /.well-known/apple-app-site-association {
default_type application/json;
}
location /.well-known/assetlinks.json {
default_type application/json;
}
Vercel (vercel.json)
{
"headers": [
{
"source": "/.well-known/apple-app-site-association",
"headers": [
{
"key": "Content-Type",
"value": "application/json"
}
]
}
]
}
Netlify (_headers)
/.well-known/apple-app-site-association
Content-Type: application/json
/.well-known/assetlinks.json
Content-Type: application/json
Express.js
const express = require("express");
const path = require("path");
const app = express();
app.get("/.well-known/apple-app-site-association", (req, res) => {
res.setHeader("Content-Type", "application/json");
res.sendFile(
path.join(__dirname, "public/.well-known/apple-app-site-association")
);
});
app.get("/.well-known/assetlinks.json", (req, res) => {
res.setHeader("Content-Type", "application/json");
res.sendFile(
path.join(__dirname, "public/.well-known/assetlinks.json")
);
});
Deep Linking Checklist for Production
Before shipping your app with deep linking, verify each item on this list:
- Custom scheme is set in
app.jsonand works in development builds - AASA file is served at
https://yourdomain.com/.well-known/apple-app-site-associationwith correctContent-Type - assetlinks.json is served at
https://yourdomain.com/.well-known/assetlinks.jsonwith correct fingerprints - Both upload and signing key fingerprints are included in
assetlinks.jsonif using Google Play App Signing - Associated domains are configured in
app.jsonfor iOS - Intent filters with
autoVerify: trueare configured for Android - Authentication redirects preserve the deep link destination through the sign-in flow
- Cold start links work correctly (test by force-killing the app and tapping a link)
- Fallback web pages exist for each deep link URL in case the app is not installed
- Analytics tracking is in place for deep link opens
Frequently Asked Questions
Do I need to configure deep linking manually with Expo Router?
No. Expo Router automatically enables deep links for every route based on your file structure. What you do need to configure is the native plumbing: the custom URL scheme in app.json, associated domains for iOS Universal Links, intent filters for Android App Links, and the corresponding server-side verification files (AASA and assetlinks.json). The routing logic itself is handled automatically.
Why do my deep links work in the Expo Dev Client but not in a production build?
The most common cause is mismatched signing certificates. Development builds use a debug signing key while production builds use your release key. On Android, make sure your assetlinks.json includes the SHA-256 fingerprint of your production signing key, which may differ from your upload key if you use Google Play App Signing. On iOS, verify that your entitlements are correctly configured and that the AASA file references the correct Team ID and bundle identifier.
What replaced Firebase Dynamic Links for deferred deep linking?
Firebase Dynamic Links was shut down in August 2025. The recommended alternatives are dedicated deep linking services like Branch, Adjust, AppsFlyer, and Singular. These provide deferred deep linking (carrying link context through an app install), attribution, and analytics. For simpler use cases, you can implement deferred deep linking using Universal Links and App Links combined with a server-side mechanism to store and retrieve the intended destination.
Can I test Universal Links and App Links in a simulator or emulator?
Partially. Android emulators support App Links testing through adb shell commands. iOS simulators support some Universal Link testing, but the behavior is more reliable on real devices. The AASA verification process, in particular, can behave differently on simulators. For the most accurate testing, use physical devices with your production domain and verification files deployed.
How do I handle deep links that require authentication?
Use Expo Router's redirect pattern in your layout files. Wrap protected routes in a layout that checks authentication status and redirects to a sign-in screen if the user isn't authenticated. Pass the original deep link path as a query parameter (e.g., /sign-in?returnTo=/orders/456) so you can redirect the user to their intended destination after they sign in.