One Logo Pool Ball
wwwwwwwwwwwwwwwwwww

Deep Linking

Deep linking lets external URLs open a specific screen in your app on web, iOS, and Android. One handles deep links by stripping a configured prefix from the incoming URL and matching the remaining path against your file-system routes.

Quick Start

Configure router.linking in your Vite config and register the same scheme in your native app manifest.

vite.config.ts
import { one } from 'one/vite'
export default {
plugins: [
one({
native: {
app: {
name: 'MyApp',
scheme: 'myapp',
ios: { bundleId: 'com.example.myapp' },
android: { applicationId: 'com.example.myapp' },
},
},
router: {
linking: {
scheme: 'myapp',
prefixes: ['https://example.com/app'],
},
},
}),
],
}

With this, opening myapp://settings/profile or https://example.com/app/settings/profile (after universal-link verification) routes to app/settings/profile.tsx.

How Prefix Matching Works

Each entry in prefixes is an exact URL prefix that gets stripped from incoming deep links before route matching. A scheme is shorthand: 'myapp' expands to 'myapp://' and 'myapp:///' automatically.

Prefixes match at clean boundaries — the URL must be exactly the prefix, or continue with /, ?, or #:

URLPrefixMatchStripped path
myapp://app/thread/1myapp://appyesthread/1
myapp://app?ref=sharemyapp://appyes?ref=share
myapp://appmyapp://appyes(empty)
myapp://application/thread/1myapp://appno(falls back)

When multiple prefixes match the same URL, the longest one wins.

The native app manifest registers URLs with iOS and Android. router.linking tells One how to strip those URLs before matching routes, so configure the scheme in both places.

OneLinkingConfig Reference

type OneLinkingConfig = {
scheme?: string | string[]
prefixes?: string[]
filter?: (url: string) => boolean
}

The getLinking Helper

getLinking is a typed identity helper for defining a linking config in TypeScript. It’s useful when you want to share the config across files or pass it programmatically.

import { getLinking } from 'one'
export const linking = getLinking({
scheme: 'myapp',
prefixes: ['https://example.com/app'],
})

Programmatic Configuration (with filter)

To use a filter function, write your own entry that calls createApp({ linking }) directly. The Vite plugin’s router.linking only accepts JSON-serializable fields.

src/entry.tsx
import { createApp, getLinking } from 'one'
const linking = getLinking({
scheme: 'myapp',
prefixes: ['https://example.com/app'],
filter: (url) => !url.includes('/admin'),
})
createApp({
routes: import.meta.glob('./app/**/*'),
routerRoot: 'app',
linking,
})

iOS Setup

The scheme in native.app lands in Info.plist after running one prebuild. To verify:

Terminal window
plutil -p ios/MyApp/Info.plist | grep -A 4 CFBundleURLTypes

Test a deep link against a running simulator:

Terminal window
xcrun simctl openurl booted myapp://settings/profile

For universal links (https://example.com/app/...), follow Apple’s associated domains setup and add the entitlement.

Android Setup

one prebuild registers the native.app scheme in AndroidManifest.xml. Add universal-link hosts to the generated native project, then test with:

Terminal window
adb shell am start -W -a android.intent.action.VIEW -d "myapp://settings/profile"

Web Behavior

On web, deep linking is just URL routing — no prefix-stripping is needed because the browser already provides the path via window.location. The configured prefixes and scheme are still accepted for API parity but are inert on web.

Inspecting Routes with useSitemap

Use useSitemap to render a list of all routes — handy for debug screens or generating link previews while testing deep links.

Edit this page on GitHub.