Deep Linking in React Native Was a Mess — So I Built a Package to Fix It
Deep linking is one of those things every React Native app eventually needs… and almost nobody enjoys implementing.
On paper, it’s simple:
Open a link like
myapp://profile/123→ take the user to their profile screen.
In reality, it quickly turns into a mess.
- The app might be closed (cold start)
- Or already open (warm start)
- You might need to support universal links (
https://myapp.com/...) - The user might not be authenticated yet
- You end up hardcoding URLs all over your codebase
And suddenly, something that should take an hour turns into a fragile system full of edge cases.
Most existing solutions don’t help much:
- Some are outdated
- Some are tightly coupled to a specific navigator
- None handle the full lifecycle of deep linking cleanly
So I built one:
👉 react-native-deeplink-manager
What It Solves
This package is built around one idea:
Deep linking shouldn’t depend on your navigation library.
It’s completely navigator-agnostic and handles everything from receiving a link → validating it → navigating → tracking.
Core features
- Supports custom schemes (
myapp://) and universal/app links (https://) - Pattern-based routing with params (
/user/:id) - Handles cold start + warm start automatically
- Built-in auth guards (
beforeNavigate) - Deferred deep links (store and process later)
-
Works with:
- React Navigation
- Expo Router
- Custom navigators
Type-safe link builder
Full TypeScript support
Installation
npm install react-native-deeplink-manager
# or
yarn add react-native-deeplink-manager
Quick Setup (React Navigation)
import { useEffect } from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import {
createDeepLinkHandler,
createReactNavigationAdapter,
createNavigationRef,
} from 'react-native-deeplink-manager';
const Stack = createNativeStackNavigator();
const navigationRef = createNavigationRef();
export default function App() {
useEffect(() => {
const handler = createDeepLinkHandler({
config: {
schemes: ['myapp'],
prefixes: ['https://myapp.com'],
routes: [
{ path: '/home', name: 'Home' },
{ path: '/user/:id', name: 'UserProfile' },
],
},
});
const adapter = createReactNavigationAdapter(navigationRef);
handler.initialize(adapter);
return () => handler.destroy();
}, []);
return (
<NavigationContainer ref={navigationRef}>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="UserProfile" component={UserProfileScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
That’s it.
No manual parsing. No edge-case juggling. It just works.
Route Matching Without Pain
Define your routes once:
{ path: '/user/:id', name: 'UserProfile' }
Now this:
myapp://myapp.com/user/123?ref=notification
Automatically becomes:
params = { id: '123' }
queryParams = { ref: 'notification' }
No regex. No custom parsing logic.
Auth Guards (Before Navigation Happens)
{
path: '/dashboard',
name: 'Dashboard',
beforeNavigate: async () => {
const isAuthenticated = await checkAuthStatus();
if (!isAuthenticated) {
navigateToLogin();
return false;
}
return true;
},
}
Block navigation before it happens. Redirect if needed. No hacks.
Stop Hardcoding URLs
Build links programmatically:
import { createDeepLinkBuilder } from 'react-native-deeplink-manager';
const builder = createDeepLinkBuilder('myapp', 'myapp.com');
builder.build('/user/:id', { id: '123' });
// → myapp://myapp.com/user/123
builder.build('/search', undefined, { q: 'react native' });
// → myapp://myapp.com/search?q=react%20native
This keeps your links consistent and type-safe.
Deferred Deep Links
Sometimes the app isn’t ready yet — maybe:
- user isn’t logged in
- navigation isn’t mounted
Store the link and process it later:
handler.setPendingLink('myapp://myapp.com/dashboard');
// later...
const link = handler.getPendingLink();
if (link) {
await handler.handleLink(link);
handler.clearPendingLink();
}
Analytics Hooks
Track everything:
const handler = createDeepLinkHandler({
config: {
schemes: ['myapp'],
routes: [...],
onLinkNotMatched: (url) => {
analytics.track('deep_link_not_matched', { url });
},
},
onColdStart: (url, match) => {
analytics.track('app_opened_from_link', { url });
},
onWarmStart: (url, match) => {
analytics.track('in_app_deep_link', { url });
},
});
Works Beyond React Navigation
Using Expo Router?
const adapter = createExpoRouterAdapter(router);
handler.initialize(adapter);
Using something custom?
const myAdapter = async (match) => {
MyNavigator.navigate(match.route.name, {
...match.params,
...match.queryParams,
});
};
handler.initialize(myAdapter);
Why Not Just Use React Navigation Linking?
React Navigation’s linking prop is useful—but limited:
- Locked into React Navigation
- No auth guards
- No link builder
- No deferred links
- No lifecycle hooks
This package focuses on the entire deep linking lifecycle, not just navigation.
Links
- npm: https://www.npmjs.com/package/react-native-deeplink-manager
- GitHub: https://github.com/oluseyi-ged/react-native-deep-link-handler
Final Thought
Deep linking shouldn’t feel like stitching together edge cases.
It should feel like routing.
That’s the gap this package tries to close.
Top comments (0)