linking은 들어온 URL을 어느 화면 + 어떤 파라미터로 보낼지 정의하는 번역 규칙이다. 웹에서 URL이 페이지를 가리키듯, 앱에서도 example://user/42 같은 URL로 특정 화면을 직접 여는 기능을 딥링크(deep link)라 하고, React Navigation에서 그 변환을 맡는 설정이 linking이다.
링크 하나가 화면에 닿기까지는 두 영역으로 나뉜다.
example://user/42가 눌렸을 때, 운영체제가 "이건 우리 앱이 처리할 링크다"라고 판단해 앱을 깨우고 URL을 넘겨주는 부분이다. iOS의 Info.plist, Android의 AndroidManifest.xml 같은 네이티브 설정이 담당한다. React Navigation은 여기 관여하지 않는다.linking이다. 양방향이라는 점이 중요하다. URL을 화면으로 바꾸기도 하고(들어올 때), 현재 화면을 공유용 URL로 만들기도 한다(나갈 때).링크가 들어온다 (example://user/42)
↓
OS가 앱에 전달한다 ← 네이티브 설정이 담당 (RN 밖)
↓
prefixes로 거른다 ┐
↓ │ React Navigation의
경로를 화면에 매칭한다 ┘ linking이 담당
↓
화면을 연다 (Profile, userId=42)
linking만 잘 적어도 OS가 URL을 안 넘겨주면 앱에 도달조차 못 하고, 네이티브만 설정하고 linking이 없으면 URL이 들어와도 어느 화면인지 매칭이 안 된다. 둘 다 있어야 동작한다 (8번 항목 참고).
이게 정적 API의 가장 큰 변화다. 위에서 본 "URL을 화면에 매칭하는 규칙"을 어디에 적느냐가 동적 API와 다르다.
동적(Dynamic) API에서는 화면 트리를 한 벌 만들고, 그것과 별도로 linking 규칙표(config)를 손으로 또 한 벌 적었다. 트리가 두 벌(네비게이터 한 벌, linking config 한 벌)이라, 구조가 바뀔 때마다 양쪽을 맞춰야 했다.
정적(Static) API에서는 각 화면 옆에 경로를 함께 적고, createStaticNavigation이 그것을 모아 linking 설정을 자동으로 만들어 준다. 즉 linking config가 네비게이터 정의에서 파생된다. 트리를 한 벌만 유지하면 된다.
요약하면:
linking.config 객체를 각각 유지한다.linking 경로를 적고, 전체 config는 자동 조립된다. 컨테이너에는 prefixes와 enabled만 넘긴다.import { createStaticNavigation } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
const RootStack = createNativeStackNavigator({
screens: {
Home: {
screen: HomeScreen,
},
Profile: {
screen: ProfileScreen,
linking: 'user/:userId',
},
Chat: {
screen: ChatScreen,
linking: 'chat/:chatId',
},
Settings: {
screen: SettingsScreen,
},
},
});
const Navigation = createStaticNavigation(RootStack);
const linking = {
enabled: 'auto' as const,
prefixes: ['https://example.com', 'example://'],
};
function App() {
return <Navigation linking={linking} fallback={<Text>Loading…</Text>} />;
}
createStaticNavigation(RootStack)이 반환하는 컴포넌트는 NavigationContainer와 동등하다. 즉 NavigationContainer가 받던 props/ref를 거의 그대로 받고, 앱 루트에 한 번만 렌더한다. 단, linking에 관해서는 차이가 있다 (다음 절).
linking 값의 속성 — 실무 중심컨테이너에 넘기는 linking 객체에 들어가는 속성들이다. 동적/정적 어느 쪽에서 쓰는지 함께 표시했다.
prefixes — 기본, 양쪽 API 공통앱이 처리할 URL 접두사 목록이다.
prefixes: ['https://example.com', 'example://'];
하는 일은 두 가지다.
example://user/42에서 example://를 떼면 user/42가 남고, 이걸 화면 경로와 맞춘다.주의: 여기 적은 값이 네이티브에 설정한 스킴/도메인과 정확히 일치해야 한다.
example://라고 적었는데Info.plist에는myapp://로 등록했다면 링크가 매칭되지 않는다.
config — 동적 API 전용URL 경로와 화면을 잇는 규칙 트리다. 화면 구조를 그대로 미러링하는 모양이다.
// 동적 API
const linking = {
prefixes: ['https://example.com', 'example://'],
config: {
screens: {
Profile: 'user/:userId',
Chat: 'chat/:chatId',
},
},
};
정적 API에서는 config를 컨테이너에 넘기지 않는다. 경로는 이미 각 화면의 linking 속성에 들어 있기 때문이다. 정적 API의 linking은 다음처럼 단출하다.
// 정적 API
const linking = {
enabled: 'auto', // 또는 true / false
prefixes: ['https://example.com', 'example://'],
};
(앱 일부만 정적인 혼용 구조라면 config 안에 정적 네비게이터의 경로를 끼워 넣을 수 있다 — 6번 참고.)
enabled — 정적 API의 핵심, 경로를 어떻게 파생할지 결정한다세 값이 있다.
'auto' — 자동 경로 생성. 모든 화면에 대해 경로를 자동 생성한다. 화면 이름을 케밥-케이스로 바꿔 경로로 쓴다.
const RootStack = createNativeStackNavigator({
screens: {
Home: { screen: HomeScreen }, // 생성 경로: ''
Profile: { screen: ProfileScreen }, // 생성 경로: 'profile'
NewsFeed: { screen: NewsFeedScreen }, // 생성 경로: 'news-feed'
},
});
화면에 linking을 명시하면 그 화면은 자동 생성 대신 명시한 경로를 쓴다. 즉 자동 + 수동을 섞을 수 있다. 파라미터가 필요한 화면(user/:userId)만 직접 적고, 단순 화면은 자동에 맡기는 식이 편하다. 단, 모든 화면이 URL로 열리므로 내부 전용 화면까지 노출될 위험이 있다.
true — 명시한 경로만. 자동 생성을 끄고, 화면에 linking으로 명시한 경로만 활성화한다. 의도하지 않은 경로가 외부에 노출되는 걸 막고 싶을 때 쓴다.
false — linking 비활성화. 딥링크 자체를 끈다.
실무 권장: 공개 딥링크 표면을 통제하려면
enabled: true+ 화면별 명시 경로 조합이 안전하다. 개발 초기 빠른 셋업이나 내부용이면'auto'가 편하다.
initialRouteName — 뒤로가기 안전장치위에서 본 네 속성(prefixes·config·enabled·고급)에는 들어가지 않지만 실무에서 자주 만난다. user/42로 앱에 곧장 진입했을 때, 뒤로가기를 누르면 빈 화면이 나오는 문제를 막아준다. 'Home'을 지정해 두면 딥링크로 들어와도 스택 맨 아래에 Home이 깔려 있어서, 자연스럽게 홈으로 돌아갈 수 있다.
대부분 위 네 개로 충분하지만, 필요할 때 쓰는 것들이다.
getStateFromPath / getPathFromState : URL ↔ 네비게이션 상태 변환 로직 전체를 직접 구현한다.getInitialURL / subscribe : 초기 URL을 어떻게 가져오고 실행 중 링크를 어떻게 구독할지 커스텀한다. 푸시 알림에서 받은 링크를 흘려보낼 때 쓴다.linking 작성법경로만 필요하면 문자열로 적는다.
Profile: {
screen: ProfileScreen,
linking: 'user/:userId',
},
파라미터를 변환해야 하면 객체로 적는다. path, parse, stringify, exact 등을 둔다.
Profile: {
screen: ProfileScreen,
linking: {
path: 'user/:userId',
parse: {
userId: (id: string) => id.replace(/^@/, ''), // URL → 파라미터 변환
},
stringify: {
userId: (id: string) => `@${id}`, // 파라미터 → URL 변환
},
},
},
네비게이터가 중첩되면 각 하위 네비게이터의 화면에도 linking을 적는다. createStaticNavigation이 트리를 따라 내려가며 전체 경로를 조립하므로, 부모-자식 경로가 자동으로 합쳐진다. 동적 API처럼 중첩 config.screens를 손으로 미러링할 필요가 없다.
enabled 동작 요약enabled | 동작 | 언제 쓰나 |
|---|---|---|
'auto' | 모든 화면 경로 자동 생성 (명시 경로는 그것 우선) | 빠른 셋업, 내부용 |
true | 화면에 명시한 경로만 활성화 | 공개 표면 통제 |
false | 딥링크 비활성화 | 딥링크 안 쓸 때 |
createPathConfigForStaticNavigation앱 일부만 정적이고 루트는 동적 NavigationContainer인 혼용 구조라면, 정적 하위 네비게이터의 경로 설정을 동적 linking config 안에 끼워 넣어야 한다. 이때 createPathConfigForStaticNavigation을 쓴다.
import { createPathConfigForStaticNavigation } from '@react-navigation/native';
const feedScreens = createPathConfigForStaticNavigation(FeedTabs);
const linking = {
prefixes: ['https://example.com', 'example://'],
config: {
screens: {
Home: '',
Feed: {
path: 'feed',
screens: feedScreens, // 정적 네비게이터에서 파생된 경로 설정
},
},
},
};
이 함수는 정적 네비게이터 설정을 받아 동적 linking config에 넣을 수 있는 path config 객체로 변환한다. 앱 전체가 정적이라면 이 함수는 필요 없다 — 컨테이너가 알아서 한다.
정적 API의 또 다른 이점이다. 동적 API는 RootStackParamList를 따로 선언하고 각 화면에 prop 타입을 달아야 했다. 정적 API에서는 네비게이터 정의(=linking 경로 포함)에서 파라미터 타입이 추론된다.
import type { StaticParamList } from '@react-navigation/native';
const RootStack = createNativeStackNavigator({
screens: {
Home: { screen: HomeScreen },
Profile: { screen: ProfileScreen, linking: 'user/:userId' },
},
});
// 루트 타입을 전역에 등록하면 useNavigation 등 어디서나 추론된다
type RootStackParamList = StaticParamList<typeof RootStack>;
declare global {
namespace ReactNavigation {
interface RootParamList extends RootStackParamList {}
}
}
경로의 :userId 같은 파라미터가 타입에 반영되므로, 네비게이션 호출 시 타입 안전성이 자동으로 따라온다.
0번에서 본 "OS 영역"이 이 부분이다. linking 설정은 들어온 URL을 어떤 화면으로 보낼지만 정의한다. OS가 그 URL을 앱으로 전달하게 하려면 네이티브 설정이 따로 필요하다. React Navigation이 아니라 플랫폼 영역이다.
설정 위치만 짧게 정리한다.
Info.plist(URL Types — 커스텀 스킴) / Associated Domains + 서버의 AASA(유니버설 링크) / 실행 중 수신은 AppDelegate 핸들러AndroidManifest.xml의 intent-filter(커스텀 스킴·딥링크) / autoVerify + 서버의 assetlinks.json(앱 링크)원칙은 하나다. prefixes에 적은 값과 네이티브에 등록한 스킴/도메인이 정확히 일치해야 한다. 어긋나면 링크가 앱에 도달하지 않거나, 도달해도 매칭되지 않는다.
xcrun simctl openurl booted "example://user/42"adb shell am start -W -a android.intent.action.VIEW -d "example://user/42" <패키지명>enabled: true + 화면별 명시 경로 조합이 안전하다. 'auto'는 모든 화면을 경로로 노출하므로, 내부 전용 화면까지 열릴 수 있다.Linking이 둘 다 다루지만 네이티브 핸들러 연결이 빠지면 한쪽만 동작할 수 있다.@nickname)와 내부 파라미터(nickname)가 다르면 화면 linking의 parse/stringify로 변환을 캡슐화한다. 화면 컴포넌트는 변환을 신경 쓰지 않게 한다.| 항목 | 동적 API | 정적 API |
|---|---|---|
| 경로 정의 위치 | 별도 linking.config 트리 | 각 화면의 linking 속성 |
| 컨테이너 | <NavigationContainer linking={{prefixes, config}}> | createStaticNavigation 컴포넌트 + {prefixes, enabled} |
| 전체 config 전달 | 가능 | 불가 (화면에서 파생) |
| 자동 경로 생성 | 없음 | enabled: 'auto' |
| 타입 | RootStackParamList 수동 선언 | StaticParamList로 추론 |
| 혼용 다리 | — | createPathConfigForStaticNavigation |
핵심은 두 문장이다.
linking은 URL ↔ 화면의 번역 규칙이고, 이게 동작하려면 OS가 URL을 앱에 넘기는 네이티브 설정이 별도로 갖춰져야 한다.prefixes와 enabled만 넘기면, 나머지 매칭 설정과 타입은 createStaticNavigation이 조립한다.